From 416ae8874378f63ecc9b635d821e54a442129b7a Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Thu, 31 Aug 2023 12:42:02 +0530 Subject: [PATCH 01/15] Optimizer for XM4 added --- .gitignore | 4 ++++ Client/CommandSerializer.cpp | 26 ++++++++++++++++++++++---- Client/CommandSerializer.h | 1 + Client/Constants.h | 9 ++++++++- Client/CrossPlatformGUI.cpp | 29 ++++++++++++++++++++++++++++- Client/CrossPlatformGUI.h | 2 ++ Client/Headphones.cpp | 31 ++++++++++++++++++++++++++++++- Client/Headphones.h | 7 +++++++ 8 files changed, 102 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 32ccd08..daf41ad 100644 --- a/.gitignore +++ b/.gitignore @@ -530,3 +530,7 @@ Client/imgui.ini # linux build Client/linux/sonyheadphonesclient.build/ + +# build folders +build-win/ +build-linux/ diff --git a/Client/CommandSerializer.cpp b/Client/CommandSerializer.cpp index 6dd30fe..c16b0d4 100644 --- a/Client/CommandSerializer.cpp +++ b/Client/CommandSerializer.cpp @@ -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 + * 0xeC 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 @@ -171,6 +179,16 @@ namespace CommandSerializer return val; } + Buffer serializeXM4OptimizeCommand(OPTIMIZER_STATE state) + { + Buffer ret; + ret.push_back(static_cast(COMMAND_TYPE::XM4_OPTIMIZER_PARAM)); + ret.push_back(static_cast(0x01)); + ret.push_back(static_cast(0x00)); + ret.push_back(static_cast(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; diff --git a/Client/CommandSerializer.h b/Client/CommandSerializer.h index 49e0aec..8a2b013 100644 --- a/Client/CommandSerializer.h +++ b/Client/CommandSerializer.h @@ -39,6 +39,7 @@ namespace CommandSerializer Message 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); } diff --git a/Client/Constants.h b/Client/Constants.h index fcdab63..89f243a 100644 --- a/Client/Constants.h +++ b/Client/Constants.h @@ -86,7 +86,8 @@ 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 }; enum class VPT_PRESET_ID : signed char @@ -128,3 +129,9 @@ enum class VPT_INQUIRED_TYPE : signed char SOUND_POSITION = 2, OUT_OF_RANGE = -1 }; + +enum class OPTIMIZER_STATE : signed char +{ + IDLE = 0, + OPTIMIZING = 1 +}; diff --git a/Client/CrossPlatformGUI.cpp b/Client/CrossPlatformGUI.cpp index 5fa4498..184d727 100644 --- a/Client/CrossPlatformGUI.cpp +++ b/Client/CrossPlatformGUI.cpp @@ -27,7 +27,14 @@ bool CrossPlatformGUI::performGUIPass() { ImGui::Spacing(); this->_drawASMControls(); - this->_drawSurroundControls(); + if (this->_bt.isConnected() && (std::string(this->_connectedDevice.name.c_str()) == "WH-1000XM4")){ + ImGui::Separator(); + this->_drawOptimizerButton(); + } + else { + this->_drawSurroundControls(); + } + this->_setHeadphoneSettings(); } } @@ -113,6 +120,8 @@ void CrossPlatformGUI::_drawDeviceDiscovery() { this->_connectedDevice = connectedDevices[selectedDevice]; this->_connectFuture.setFromAsync([this]() { this->_bt.connect(this->_connectedDevice.mac); }); + + // Add post-connection setup here } } } @@ -211,6 +220,24 @@ void CrossPlatformGUI::_drawSurroundControls() } } +void CrossPlatformGUI::_drawOptimizerButton() +{ + if (this->_headphones.getOptimizerState() == OPTIMIZER_STATE::IDLE) + { + if (ImGui::Button("Optimize")) + { + this->_headphones.setOptimizerState(OPTIMIZER_STATE::OPTIMIZING); + } + } + else { + // TODO: Change button to show cancel option while optimizing state + if (ImGui::Button("Optimize")) + { + this->_headphones.setOptimizerState(OPTIMIZER_STATE::IDLE); + } + } +} + void CrossPlatformGUI::_setHeadphoneSettings() { //Don't show if the command only takes a few frames to send static int commandLinger = 0; diff --git a/Client/CrossPlatformGUI.h b/Client/CrossPlatformGUI.h index e24dbaa..d92b41d 100644 --- a/Client/CrossPlatformGUI.h +++ b/Client/CrossPlatformGUI.h @@ -12,6 +12,7 @@ #include "Headphones.h" #include +#include constexpr auto GUI_MAX_MESSAGES = 5; constexpr auto GUI_HEIGHT = 380; @@ -36,6 +37,7 @@ class CrossPlatformGUI void _drawASMControls(); void _drawSurroundControls(); void _setHeadphoneSettings(); + void _drawOptimizerButton(); BluetoothDevice _connectedDevice; BluetoothWrapper _bt; diff --git a/Client/Headphones.cpp b/Client/Headphones.cpp index 9c92f95..065bf45 100644 --- a/Client/Headphones.cpp +++ b/Client/Headphones.cpp @@ -50,6 +50,17 @@ int Headphones::getAsmLevel() return this->_asmLevel.current; } +void Headphones::setOptimizerState(OPTIMIZER_STATE val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_optimizerState.desired = val; +} + +OPTIMIZER_STATE Headphones::getOptimizerState() +{ + return this->_optimizerState.current; +} + void Headphones::setSurroundPosition(SOUND_POSITION_PRESET val) { std::lock_guard guard(this->_propertyMtx); @@ -74,11 +85,29 @@ int Headphones::getVptType() bool Headphones::isChanged() { - return !(this->_ambientSoundControl.isFulfilled() && this->_asmLevel.isFulfilled() && this->_focusOnVoice.isFulfilled() && this->_surroundPosition.isFulfilled() && this->_vptType.isFulfilled()); + return !(this->_ambientSoundControl.isFulfilled() && + this->_asmLevel.isFulfilled() && + this->_focusOnVoice.isFulfilled() && + this->_surroundPosition.isFulfilled() && + this->_vptType.isFulfilled() && + this->_optimizerState.isFulfilled() + ); } void Headphones::setChanges() { + if (!(this->_optimizerState.isFulfilled())) + { + auto state = this->_optimizerState.desired; + + this->_conn.sendCommand(CommandSerializer::serializeXM4OptimizeCommand( + state + )); + + std::lock_guard guard(this->_propertyMtx); + this->_optimizerState.fulfill(); + } + if (!(this->_ambientSoundControl.isFulfilled() && this->_focusOnVoice.isFulfilled() && this->_asmLevel.isFulfilled())) { auto ncAsmEffect = this->_ambientSoundControl.desired ? NC_ASM_EFFECT::ADJUSTMENT_COMPLETION : NC_ASM_EFFECT::OFF; diff --git a/Client/Headphones.h b/Client/Headphones.h index b3f20f4..8606c10 100644 --- a/Client/Headphones.h +++ b/Client/Headphones.h @@ -28,6 +28,9 @@ class Headphones { void setAsmLevel(int val); int getAsmLevel(); + void setOptimizerState(OPTIMIZER_STATE val); + OPTIMIZER_STATE getOptimizerState(); + void setSurroundPosition(SOUND_POSITION_PRESET val); SOUND_POSITION_PRESET getSurroundPosition(); @@ -40,8 +43,12 @@ class Headphones { Property _ambientSoundControl = { 0 }; Property _focusOnVoice = { 0 }; Property _asmLevel = { 0 }; + Property _surroundPosition = { SOUND_POSITION_PRESET::OUT_OF_RANGE, SOUND_POSITION_PRESET::OFF }; Property _vptType = { 0 }; + + Property _optimizerState = { OPTIMIZER_STATE::IDLE }; + std::mutex _propertyMtx; BluetoothWrapper& _conn; From 88877fbcdcae5aa58620fc7dd1602f0004771b63 Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Thu, 31 Aug 2023 17:53:53 +0530 Subject: [PATCH 02/15] XM4 Speak To Chat Added --- Client/BluetoothWrapper.cpp | 11 +++++++ Client/BluetoothWrapper.h | 3 +- Client/CommandSerializer.cpp | 21 +++++++++++++ Client/CommandSerializer.h | 2 ++ Client/Constants.h | 11 ++++++- Client/CrossPlatformGUI.cpp | 51 ++++++++++++++++++++++++++++++-- Client/CrossPlatformGUI.h | 2 ++ Client/Headphones.cpp | 57 +++++++++++++++++++++++++++++++++++- Client/Headphones.h | 8 +++++ 9 files changed, 161 insertions(+), 5 deletions(-) diff --git a/Client/BluetoothWrapper.cpp b/Client/BluetoothWrapper.cpp index 6c25711..e8f0123 100644 --- a/Client/BluetoothWrapper.cpp +++ b/Client/BluetoothWrapper.cpp @@ -33,6 +33,17 @@ int BluetoothWrapper::sendCommand(const std::vector& bytes) return bytesSent; } +int BluetoothWrapper::sendCommand(const std::vector& bytes, DATA_TYPE dtype) +{ + std::lock_guard guard(this->_connectorMtx); + auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber++); + auto bytesSent = this->_connector->send(data.data(), data.size()); + + this->_waitForAck(); + + return bytesSent; +} + bool BluetoothWrapper::isConnected() noexcept { return this->_connector->isConnected(); diff --git a/Client/BluetoothWrapper.h b/Client/BluetoothWrapper.h index 7c7c044..b554cfa 100644 --- a/Client/BluetoothWrapper.h +++ b/Client/BluetoothWrapper.h @@ -22,6 +22,7 @@ class BluetoothWrapper BluetoothWrapper& operator=(BluetoothWrapper&& other) noexcept; int sendCommand(const std::vector& bytes); + int sendCommand(const std::vector& bytes, DATA_TYPE dtype); bool isConnected() noexcept; //Try to connect to the headphones @@ -36,4 +37,4 @@ class BluetoothWrapper std::unique_ptr _connector; std::mutex _connectorMtx; unsigned int _seqNumber = 0; -}; \ No newline at end of file +}; diff --git a/Client/CommandSerializer.cpp b/Client/CommandSerializer.cpp index c16b0d4..6ac563c 100644 --- a/Client/CommandSerializer.cpp +++ b/Client/CommandSerializer.cpp @@ -213,5 +213,26 @@ namespace CommandSerializer return ret; } + Buffer serializeXM4SpeakToChat(S2C_TOGGLE s2cState) + { + Buffer ret; + ret.push_back(static_cast(COMMAND_TYPE::XM4_S2C_TOGGLE_PARAM)); + ret.push_back(static_cast(0x05)); + ret.push_back(static_cast(0x01)); + ret.push_back(static_cast(s2cState)); + return ret; + } + + Buffer serializeXM4_S2C_Options(unsigned char sensitivity, unsigned char voice, unsigned char offTime) + { + Buffer ret; + ret.push_back(static_cast(COMMAND_TYPE::XM4_S2C_OPTIONS_PARAM)); + ret.push_back(static_cast(0x05)); + ret.push_back(static_cast(0x00)); + ret.push_back(static_cast(sensitivity)); + ret.push_back(static_cast(voice)); + ret.push_back(static_cast(offTime)); + return ret; + } } diff --git a/Client/CommandSerializer.h b/Client/CommandSerializer.h index 8a2b013..828301a 100644 --- a/Client/CommandSerializer.h +++ b/Client/CommandSerializer.h @@ -42,5 +42,7 @@ namespace CommandSerializer 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); } diff --git a/Client/Constants.h b/Client/Constants.h index 89f243a..6cfc9ca 100644 --- a/Client/Constants.h +++ b/Client/Constants.h @@ -87,7 +87,9 @@ enum class COMMAND_TYPE : signed char { VPT_SET_PARAM = 72, NCASM_SET_PARAM = 104, - XM4_OPTIMIZER_PARAM = (signed char) 0x84 + XM4_OPTIMIZER_PARAM = (signed char) 0x84, + XM4_S2C_TOGGLE_PARAM = (signed char) 0xf8, + XM4_S2C_OPTIONS_PARAM = (signed char) 0xfc, }; enum class VPT_PRESET_ID : signed char @@ -135,3 +137,10 @@ enum class OPTIMIZER_STATE : signed char IDLE = 0, OPTIMIZING = 1 }; + +enum class S2C_TOGGLE : signed char +{ + ACTIVE = 1, + INACTIVE = 0 +}; + diff --git a/Client/CrossPlatformGUI.cpp b/Client/CrossPlatformGUI.cpp index 184d727..aefc5be 100644 --- a/Client/CrossPlatformGUI.cpp +++ b/Client/CrossPlatformGUI.cpp @@ -25,10 +25,11 @@ bool CrossPlatformGUI::performGUIPass() if (this->_bt.isConnected()) { - ImGui::Spacing(); + // ImGui::Spacing(); + ImGui::Separator(); this->_drawASMControls(); if (this->_bt.isConnected() && (std::string(this->_connectedDevice.name.c_str()) == "WH-1000XM4")){ - ImGui::Separator(); + this->_drawSpeakToChat(); this->_drawOptimizerButton(); } else { @@ -238,6 +239,52 @@ void CrossPlatformGUI::_drawOptimizerButton() } } +void CrossPlatformGUI::_drawSpeakToChat() +{ + enum Sensitivity { Sens_Auto, Sens_High, Sens_Low, Sens_count}; + const char* Sens_hints[Sens_count] = { "Auto", "High", "Low" }; + + enum AutoOff { Time_Short, Time_Std, Time_Long, Time_Inf, Time_count}; + const char* Time_hints[Time_count] = { "Short", "Standard", "Long", "Off" }; + + static bool S2Ctoggle_check = true; + static int S2C_Sens = Sens_Low; + static bool S2C_Voice = 0; + static int S2C_AutoOff = Time_Short; + + const char* Sens_name = (S2C_Sens >= 0 && S2C_Sens < Sens_count) ? Sens_hints[S2C_Sens] : "Unknown"; + const char* Time_name = (S2C_AutoOff >= 0 && S2C_AutoOff < Time_count) ? Time_hints[S2C_AutoOff] : "Unknown"; + // S2Ctoggle = (this->_headphones.getS2CToggle() == S2C_TOGGLE::ACTIVE) ? true : false; + // S2C_Sens = (this->_headphones.getS2COptions() & 0x00ff0000)>>16; + // S2C_Voice = (this->_headphones.getS2COptions() & 0x0000ff00)>>8; + // S2C_AutoOff = (this->_headphones.getS2COptions() & 0x000000ff); + + if (ImGui::CollapsingHeader("Speak To Chat Controls", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("Speak To Chat", &S2Ctoggle_check); + + if (S2Ctoggle_check) + { + if (S2Ctoggle_check){ + // ImGui::SameLine(); + ImGui::SliderInt("Speak to chat sensitivity", &S2C_Sens, 0, Sens_count - 1, Sens_name); + ImGui::SliderInt("Speak to chat Auto close", &S2C_AutoOff, 0, Time_count - 1, Time_name); + ImGui::Checkbox("Voice passthrough", &S2C_Voice); + } + else { + } + } + } + + if (S2Ctoggle_check) + this->_headphones.setS2CToggle(S2C_TOGGLE::ACTIVE); + else + this->_headphones.setS2CToggle(S2C_TOGGLE::INACTIVE); + + this->_headphones.setS2COptions(S2C_Sens, S2C_Voice, S2C_AutoOff); + +} + void CrossPlatformGUI::_setHeadphoneSettings() { //Don't show if the command only takes a few frames to send static int commandLinger = 0; diff --git a/Client/CrossPlatformGUI.h b/Client/CrossPlatformGUI.h index d92b41d..dcc2555 100644 --- a/Client/CrossPlatformGUI.h +++ b/Client/CrossPlatformGUI.h @@ -11,6 +11,7 @@ #include "CascadiaCodeFont.h" #include "Headphones.h" +#include #include #include @@ -38,6 +39,7 @@ class CrossPlatformGUI void _drawSurroundControls(); void _setHeadphoneSettings(); void _drawOptimizerButton(); + void _drawSpeakToChat(); BluetoothDevice _connectedDevice; BluetoothWrapper _bt; diff --git a/Client/Headphones.cpp b/Client/Headphones.cpp index 065bf45..ee71ac3 100644 --- a/Client/Headphones.cpp +++ b/Client/Headphones.cpp @@ -61,6 +61,30 @@ OPTIMIZER_STATE Headphones::getOptimizerState() return this->_optimizerState.current; } +void Headphones::setS2CToggle(S2C_TOGGLE val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_speakToChat.desired = val; +} + +void Headphones::setS2COptions(int sensitivity, bool voice, int offTime) +{ + unsigned int sens = (unsigned int) sensitivity; + unsigned int time = (unsigned int) offTime; + std::lock_guard guard(this->_propertyMtx); + this->_s2cOptions.desired = { (sens << 16) | (voice << 8) | (time) }; +} + +S2C_TOGGLE Headphones::getS2CToggle() +{ + return this->_speakToChat.current; +} + +unsigned int Headphones::getS2COptions() +{ + return this->_s2cOptions.current; +} + void Headphones::setSurroundPosition(SOUND_POSITION_PRESET val) { std::lock_guard guard(this->_propertyMtx); @@ -90,7 +114,9 @@ bool Headphones::isChanged() this->_focusOnVoice.isFulfilled() && this->_surroundPosition.isFulfilled() && this->_vptType.isFulfilled() && - this->_optimizerState.isFulfilled() + this->_optimizerState.isFulfilled() && + this->_speakToChat.isFulfilled() && + this->_s2cOptions.isFulfilled() ); } @@ -108,6 +134,35 @@ void Headphones::setChanges() this->_optimizerState.fulfill(); } + if (!(this->_speakToChat.isFulfilled())) + { + auto s2cState = this->_speakToChat.desired; + + this->_conn.sendCommand(CommandSerializer::serializeXM4SpeakToChat( + s2cState + )); + + std::lock_guard guard(this->_propertyMtx); + this->_speakToChat.fulfill(); + } + + if (!(this->_s2cOptions.isFulfilled())) + { + auto s2cOptions = this->_s2cOptions.desired; + unsigned char sensitivity = (unsigned char) (s2cOptions >> 16) & 0xff; + unsigned char voice = (unsigned char) (s2cOptions >> 8) & 0xff; + unsigned char offTime = (unsigned char) (s2cOptions) & 0xff; + + this->_conn.sendCommand(CommandSerializer::serializeXM4_S2C_Options( + sensitivity, + voice, + offTime + )); + + std::lock_guard guard(this->_propertyMtx); + this->_s2cOptions.fulfill(); + } + if (!(this->_ambientSoundControl.isFulfilled() && this->_focusOnVoice.isFulfilled() && this->_asmLevel.isFulfilled())) { auto ncAsmEffect = this->_ambientSoundControl.desired ? NC_ASM_EFFECT::ADJUSTMENT_COMPLETION : NC_ASM_EFFECT::OFF; diff --git a/Client/Headphones.h b/Client/Headphones.h index 8606c10..98fe429 100644 --- a/Client/Headphones.h +++ b/Client/Headphones.h @@ -31,6 +31,11 @@ class Headphones { void setOptimizerState(OPTIMIZER_STATE val); OPTIMIZER_STATE getOptimizerState(); + void setS2CToggle(S2C_TOGGLE val); + void setS2COptions(int sensitivity, bool voice, int offTime); + S2C_TOGGLE getS2CToggle(); + unsigned int getS2COptions(); + void setSurroundPosition(SOUND_POSITION_PRESET val); SOUND_POSITION_PRESET getSurroundPosition(); @@ -48,6 +53,9 @@ class Headphones { Property _vptType = { 0 }; Property _optimizerState = { OPTIMIZER_STATE::IDLE }; + + Property _speakToChat = { S2C_TOGGLE::INACTIVE }; + Property _s2cOptions = { 0 }; std::mutex _propertyMtx; From 7614b25a4697c23c8452d5abc5c28506cca296f2 Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Sat, 2 Sep 2023 20:53:54 +0530 Subject: [PATCH 03/15] Added some structure for Listener (incoming packet handler) --- Client/BluetoothWrapper.cpp | 11 ++++++++-- Client/BluetoothWrapper.h | 2 ++ Client/CommandSerializer.cpp | 6 ++++-- Client/CommandSerializer.h | 19 ++++++++-------- Client/Headphones.h | 7 ++++++ Client/Listener.cpp | 37 +++++++++++++++++++++++++++++++ Client/Listener.h | 42 ++++++++++++++++++++++++++++++++++++ 7 files changed, 111 insertions(+), 13 deletions(-) create mode 100644 Client/Listener.cpp create mode 100644 Client/Listener.h diff --git a/Client/BluetoothWrapper.cpp b/Client/BluetoothWrapper.cpp index e8f0123..1afae42 100644 --- a/Client/BluetoothWrapper.cpp +++ b/Client/BluetoothWrapper.cpp @@ -39,7 +39,8 @@ int BluetoothWrapper::sendCommand(const std::vector& bytes, DATA_TYPE dtyp auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber++); auto bytesSent = this->_connector->send(data.data(), data.size()); - this->_waitForAck(); + if (dtype != DATA_TYPE::ACK) + this->_waitForAck(); return bytesSent; } @@ -69,6 +70,11 @@ std::vector BluetoothWrapper::getConnectedDevices() } void BluetoothWrapper::_waitForAck() +{ + +} + +Buffer BluetoothWrapper::readReplies() { bool ongoingMessage = false; bool messageFinished = false; @@ -103,7 +109,8 @@ void BluetoothWrapper::_waitForAck() msgBytes.insert(msgBytes.end(), buf + messageStart, buf + messageEnd); } while (!messageFinished); + return msgBytes; + auto msg = CommandSerializer::unpackBtMessage(msgBytes); this->_seqNumber = msg.seqNumber; } - diff --git a/Client/BluetoothWrapper.h b/Client/BluetoothWrapper.h index b554cfa..3d252e3 100644 --- a/Client/BluetoothWrapper.h +++ b/Client/BluetoothWrapper.h @@ -24,6 +24,8 @@ class BluetoothWrapper int sendCommand(const std::vector& bytes); int sendCommand(const std::vector& bytes, DATA_TYPE dtype); + Buffer readReplies(); + bool isConnected() noexcept; //Try to connect to the headphones void connect(const std::string& addr); diff --git a/Client/CommandSerializer.cpp b/Client/CommandSerializer.cpp index 6ac563c..2664044 100644 --- a/Client/CommandSerializer.cpp +++ b/Client/CommandSerializer.cpp @@ -141,7 +141,7 @@ namespace CommandSerializer return ret; } - Message unpackBtMessage(const Buffer& src) + BtMessage unpackBtMessage(const Buffer& src) { //Message data format: ESCAPE_SPECIALS(<1 BYTE CHECKSUM>) auto unescaped = _unescapeSpecials(src); @@ -151,13 +151,15 @@ namespace CommandSerializer throw std::runtime_error("Invalid message: Smaller than the minimum message size"); } - Message ret; + BtMessage ret; ret.dataType = static_cast(src[0]); ret.seqNumber = src[1]; if ((unsigned char)src[src.size() - 1] != _sumChecksum(src.data(), src.size() - 1)) { throw RecoverableException("Invalid checksum!", true); } + int numMsgBytes = static_cast(src[5]); + ret.messageBytes.insert(ret.messageBytes.end(), src.begin() + 6, src.begin() + 6 + numMsgBytes); return ret; } diff --git a/Client/CommandSerializer.h b/Client/CommandSerializer.h index 828301a..bef7a35 100644 --- a/Client/CommandSerializer.h +++ b/Client/CommandSerializer.h @@ -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); @@ -36,7 +37,7 @@ 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); diff --git a/Client/Headphones.h b/Client/Headphones.h index 98fe429..ea33255 100644 --- a/Client/Headphones.h +++ b/Client/Headphones.h @@ -11,6 +11,7 @@ struct Property { void fulfill(); bool isFulfilled(); + void setState(T val); }; class Headphones { @@ -73,3 +74,9 @@ inline bool Property::isFulfilled() { return this->desired == this->current; } + +template +inline void Property::setState(T val) +{ + this->current = val; +} \ No newline at end of file diff --git a/Client/Listener.cpp b/Client/Listener.cpp new file mode 100644 index 0000000..3ce75b4 --- /dev/null +++ b/Client/Listener.cpp @@ -0,0 +1,37 @@ +#include "Listener.h" + +// Listener::Listener()//: +// // _ackRecvd(false) +// { + +// } + +void Listener::listen() +{ + // do + for(;;) + { + Buffer reply = _bt.readReplies(); + this->handle_message(reply); + }// while(1); +} + +inline BtMessage Listener::parse(Buffer msg) +{ + return CommandSerializer::unpackBtMessage(msg); +} + +void Listener::handle_message(Buffer msg) +{ + BtMessage m = this->parse(msg); + if (m.dataType == DATA_TYPE::ACK) + { + // std::condition_variable::notify_all + std::lock_guard guard(this->_listenerMtx); + this->_ackRecvd = true; + } else if (m.dataType == DATA_TYPE::DATA_MDR || m.dataType == DATA_TYPE::DATA_MDR_NO2) { + // Set these values as current values of Headphone property + // Send ACK message to real headphone to shut it up! + this->_bt.sendCommand({}, DATA_TYPE::ACK); + } +} diff --git a/Client/Listener.h b/Client/Listener.h new file mode 100644 index 0000000..29c246d --- /dev/null +++ b/Client/Listener.h @@ -0,0 +1,42 @@ +#pragma once + +#include "imgui/imgui.h" +#include "Constants.h" +#include "IBluetoothConnector.h" +#include "BluetoothWrapper.h" +#include "CommandSerializer.h" +#include "Exceptions.h" +#include "TimedMessageQueue.h" +#include "SingleInstanceFuture.h" +#include "CascadiaCodeFont.h" +#include "Headphones.h" + +#include + +class Listener +{ + /* + Listen for and read incoming messages + Parse messages + Call message handler + */ + /* + have a valid bit for ack messages + - when command is sent, it is set to 0 by locking + - when ack is received, set it to 1 + */ +public: + Listener(); + void listen(); + inline BtMessage parse(Buffer msg); + void handle_message(Buffer msg); + +private: + + Headphones& _headphones; + BluetoothWrapper& _bt; + + std::mutex _listenerMtx; + bool _ackRecvd; + +}; \ No newline at end of file From f26b21586ce569140a0324388cf9b15ba7bd3955 Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Sun, 3 Sep 2023 03:11:26 +0530 Subject: [PATCH 04/15] Listener code added, some message handling added --- Client/BluetoothWrapper.cpp | 15 ++++++++-- Client/BluetoothWrapper.h | 5 +++- Client/Constants.h | 5 ++++ Client/Headphones.cpp | 55 ++++++++++++++++++++++++++++++++++++- Client/Headphones.h | 4 +++ Client/Listener.cpp | 11 ++++++-- Client/Listener.h | 3 ++ 7 files changed, 91 insertions(+), 7 deletions(-) diff --git a/Client/BluetoothWrapper.cpp b/Client/BluetoothWrapper.cpp index 1afae42..305e28b 100644 --- a/Client/BluetoothWrapper.cpp +++ b/Client/BluetoothWrapper.cpp @@ -1,12 +1,14 @@ #include "BluetoothWrapper.h" -BluetoothWrapper::BluetoothWrapper(std::unique_ptr connector) +BluetoothWrapper::BluetoothWrapper(Listener * listener, std::unique_ptr connector) { + this->_listener = listener; this->_connector.swap(connector); } BluetoothWrapper::BluetoothWrapper(BluetoothWrapper&& other) noexcept { + this->_listener = other._listener; this->_connector.swap(other._connector); this->_seqNumber = other._seqNumber; } @@ -71,7 +73,10 @@ std::vector BluetoothWrapper::getConnectedDevices() void BluetoothWrapper::_waitForAck() { - + do{ + // Spin wait while ACK not received + // I want to change it later + } while (this->_listener->getAck() == false); } Buffer BluetoothWrapper::readReplies() @@ -114,3 +119,9 @@ Buffer BluetoothWrapper::readReplies() auto msg = CommandSerializer::unpackBtMessage(msgBytes); this->_seqNumber = msg.seqNumber; } + +void BluetoothWrapper::setSeqNumber(unsigned int seqNumber) +{ + std::lock_guard guard(this->_connectorMtx); + this->_seqNumber = seqNumber; +} diff --git a/Client/BluetoothWrapper.h b/Client/BluetoothWrapper.h index 3d252e3..ac229d2 100644 --- a/Client/BluetoothWrapper.h +++ b/Client/BluetoothWrapper.h @@ -1,5 +1,6 @@ #pragma once +#include "Listener.h" #include "IBluetoothConnector.h" #include "CommandSerializer.h" #include "Constants.h" @@ -13,7 +14,7 @@ class BluetoothWrapper { public: - BluetoothWrapper(std::unique_ptr connector); + BluetoothWrapper(Listener * Listener, std::unique_ptr connector); BluetoothWrapper(const BluetoothWrapper&) = delete; BluetoothWrapper& operator=(const BluetoothWrapper&) = delete; @@ -32,10 +33,12 @@ class BluetoothWrapper void disconnect() noexcept; std::vector getConnectedDevices(); + void setSeqNumber(unsigned int seqNumber); private: void _waitForAck(); + Listener * _listener; std::unique_ptr _connector; std::mutex _connectorMtx; unsigned int _seqNumber = 0; diff --git a/Client/Constants.h b/Client/Constants.h index 6cfc9ca..f0c61f5 100644 --- a/Client/Constants.h +++ b/Client/Constants.h @@ -90,6 +90,7 @@ enum class COMMAND_TYPE : signed char XM4_OPTIMIZER_PARAM = (signed char) 0x84, XM4_S2C_TOGGLE_PARAM = (signed char) 0xf8, XM4_S2C_OPTIONS_PARAM = (signed char) 0xfc, + DEVICES_QUERY_RESPONSE = (signed char) 0x37, }; enum class VPT_PRESET_ID : signed char @@ -144,3 +145,7 @@ enum class S2C_TOGGLE : signed char INACTIVE = 0 }; +enum class QUERY_RESPONSE_TYPE : signed char +{ + DEVICES = (signed char) 0x37, +}; diff --git a/Client/Headphones.cpp b/Client/Headphones.cpp index ee71ac3..c4f453e 100644 --- a/Client/Headphones.cpp +++ b/Client/Headphones.cpp @@ -3,7 +3,10 @@ #include -Headphones::Headphones(BluetoothWrapper& conn) : _conn(conn) +Headphones::Headphones(BluetoothWrapper& conn) : +_conn(conn), +_dev1({"No device", ""}), +_dev2({"No device", ""}) { } @@ -217,3 +220,53 @@ void Headphones::setChanges() this->_surroundPosition.fulfill(); } } + +void Headphones::setStateFromReply(BtMessage replyMessage) +{ + Buffer bytes = replyMessage.messageBytes; + COMMAND_TYPE cmd = static_cast(bytes[0]); + + switch (cmd) + { + case COMMAND_TYPE::DEVICES_QUERY_RESPONSE: + if (bytes[1] != 1) + // Wrong query type, break + break; + + int idx = 3; + int numDevices = static_cast(bytes[2]); + for (; numDevices > 0; numDevices--) + { + std::string mac_addr = ""; + for (int i = idx; i(bytes[idx]); + + idx++; + int name_length = static_cast(bytes[idx]); + std::string name = ""; + for (int i = idx; i_savedDevices.push_back(dev); + if (number == 1) + this->_dev1 = this->_savedDevices[this->_savedDevices.size()-1]; + if (number == 2) + this->_dev2 = this->_savedDevices[this->_savedDevices.size()-1]; + } + break; + + default: + break; + } +} diff --git a/Client/Headphones.h b/Client/Headphones.h index ea33255..c2b8b3a 100644 --- a/Client/Headphones.h +++ b/Client/Headphones.h @@ -45,6 +45,8 @@ class Headphones { bool isChanged(); void setChanges(); + + void setStateFromReply(BtMessage replyMessage); private: Property _ambientSoundControl = { 0 }; Property _focusOnVoice = { 0 }; @@ -58,6 +60,8 @@ class Headphones { Property _speakToChat = { S2C_TOGGLE::INACTIVE }; Property _s2cOptions = { 0 }; + std::vector _savedDevices; + BluetoothDevice& _dev1, & _dev2; std::mutex _propertyMtx; BluetoothWrapper& _conn; diff --git a/Client/Listener.cpp b/Client/Listener.cpp index 3ce75b4..51f2e4c 100644 --- a/Client/Listener.cpp +++ b/Client/Listener.cpp @@ -8,12 +8,11 @@ void Listener::listen() { - // do for(;;) { Buffer reply = _bt.readReplies(); this->handle_message(reply); - }// while(1); + } } inline BtMessage Listener::parse(Buffer msg) @@ -31,7 +30,13 @@ void Listener::handle_message(Buffer msg) this->_ackRecvd = true; } else if (m.dataType == DATA_TYPE::DATA_MDR || m.dataType == DATA_TYPE::DATA_MDR_NO2) { // Set these values as current values of Headphone property - // Send ACK message to real headphone to shut it up! + this->_headphones.setStateFromReply(m); this->_bt.sendCommand({}, DATA_TYPE::ACK); } } + +bool Listener::getAck() +{ + std::lock_guard guard(_listenerMtx); + return (this->_ackRecvd == true); +} \ No newline at end of file diff --git a/Client/Listener.h b/Client/Listener.h index 29c246d..4209463 100644 --- a/Client/Listener.h +++ b/Client/Listener.h @@ -12,6 +12,7 @@ #include "Headphones.h" #include +#include class Listener { @@ -30,6 +31,7 @@ class Listener void listen(); inline BtMessage parse(Buffer msg); void handle_message(Buffer msg); + bool getAck(); private: @@ -37,6 +39,7 @@ class Listener BluetoothWrapper& _bt; std::mutex _listenerMtx; + std::condition_variable _condt; bool _ackRecvd; }; \ No newline at end of file From c0be28696ff4a93919c0bf7c3eec4c8c04f0eb77 Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Sun, 3 Sep 2023 13:56:52 +0530 Subject: [PATCH 05/15] Listener object registered --- Client/BluetoothWrapper.cpp | 21 ++++++++++++++++++--- Client/BluetoothWrapper.h | 8 +++++++- Client/CrossPlatformGUI.cpp | 3 +++ Client/CrossPlatformGUI.h | 1 + Client/Headphones.h | 2 +- Client/Listener.cpp | 10 +++++----- Client/Listener.h | 8 +------- 7 files changed, 36 insertions(+), 17 deletions(-) diff --git a/Client/BluetoothWrapper.cpp b/Client/BluetoothWrapper.cpp index 305e28b..9faa6de 100644 --- a/Client/BluetoothWrapper.cpp +++ b/Client/BluetoothWrapper.cpp @@ -1,14 +1,19 @@ #include "BluetoothWrapper.h" -BluetoothWrapper::BluetoothWrapper(Listener * listener, std::unique_ptr connector) +BluetoothWrapper::BluetoothWrapper(std::unique_ptr listener, std::unique_ptr connector) +{ + this->_listener.swap(listener); + this->_connector.swap(connector); +} + +BluetoothWrapper::BluetoothWrapper(std::unique_ptr connector) { - this->_listener = listener; this->_connector.swap(connector); } BluetoothWrapper::BluetoothWrapper(BluetoothWrapper&& other) noexcept { - this->_listener = other._listener; + this->_listener.swap(other._listener); this->_connector.swap(other._connector); this->_seqNumber = other._seqNumber; } @@ -24,6 +29,16 @@ BluetoothWrapper& BluetoothWrapper::operator=(BluetoothWrapper&& other) noexcept return *this; } +void BluetoothWrapper::moveListener(std::unique_ptr listener) +{ + this->_listener = std::move(listener); +} + +void BluetoothWrapper::registerListener() +{ + auto useless_future = std::async(std::launch::async, this->_listener->listen(), this->_listener.get()); +} + int BluetoothWrapper::sendCommand(const std::vector& bytes) { std::lock_guard guard(this->_connectorMtx); diff --git a/Client/BluetoothWrapper.h b/Client/BluetoothWrapper.h index ac229d2..7f57102 100644 --- a/Client/BluetoothWrapper.h +++ b/Client/BluetoothWrapper.h @@ -8,13 +8,15 @@ #include #include #include +#include //Thread-safety: This class is thread-safe. class BluetoothWrapper { public: - BluetoothWrapper(Listener * Listener, std::unique_ptr connector); + BluetoothWrapper(std::unique_ptr listener, std::unique_ptr connector); + BluetoothWrapper(std::unique_ptr connector); BluetoothWrapper(const BluetoothWrapper&) = delete; BluetoothWrapper& operator=(const BluetoothWrapper&) = delete; @@ -22,6 +24,9 @@ class BluetoothWrapper BluetoothWrapper(BluetoothWrapper&& other) noexcept; BluetoothWrapper& operator=(BluetoothWrapper&& other) noexcept; + void moveListener(std::unique_ptr listener); + void registerListener(); + int sendCommand(const std::vector& bytes); int sendCommand(const std::vector& bytes, DATA_TYPE dtype); @@ -40,6 +45,7 @@ class BluetoothWrapper Listener * _listener; std::unique_ptr _connector; + std::unique_ptr _listener; std::mutex _connectorMtx; unsigned int _seqNumber = 0; }; diff --git a/Client/CrossPlatformGUI.cpp b/Client/CrossPlatformGUI.cpp index aefc5be..4a83c66 100644 --- a/Client/CrossPlatformGUI.cpp +++ b/Client/CrossPlatformGUI.cpp @@ -123,6 +123,9 @@ void CrossPlatformGUI::_drawDeviceDiscovery() this->_connectFuture.setFromAsync([this]() { this->_bt.connect(this->_connectedDevice.mac); }); // Add post-connection setup here + std::unique_ptr listener = std::make_unique(this->_headphones, this->_bt); + this->_bt.moveListener(std::move(listener)); + this->_bt.registerListener(); } } } diff --git a/Client/CrossPlatformGUI.h b/Client/CrossPlatformGUI.h index dcc2555..1124d88 100644 --- a/Client/CrossPlatformGUI.h +++ b/Client/CrossPlatformGUI.h @@ -10,6 +10,7 @@ #include "SingleInstanceFuture.h" #include "CascadiaCodeFont.h" #include "Headphones.h" +#include "Listener.h" #include #include diff --git a/Client/Headphones.h b/Client/Headphones.h index c2b8b3a..9960fab 100644 --- a/Client/Headphones.h +++ b/Client/Headphones.h @@ -61,7 +61,7 @@ class Headphones { Property _s2cOptions = { 0 }; std::vector _savedDevices; - BluetoothDevice& _dev1, & _dev2; + BluetoothDevice _dev1, _dev2; std::mutex _propertyMtx; BluetoothWrapper& _conn; diff --git a/Client/Listener.cpp b/Client/Listener.cpp index 51f2e4c..ba30d34 100644 --- a/Client/Listener.cpp +++ b/Client/Listener.cpp @@ -1,10 +1,10 @@ #include "Listener.h" -// Listener::Listener()//: -// // _ackRecvd(false) -// { - -// } +Listener::Listener(Headphones& headphones, BluetoothWrapper& bt): +_headphones(headphones), +_bt(bt), +_ackRecvd(false) +{} void Listener::listen() { diff --git a/Client/Listener.h b/Client/Listener.h index 4209463..b5845a0 100644 --- a/Client/Listener.h +++ b/Client/Listener.h @@ -1,18 +1,12 @@ #pragma once -#include "imgui/imgui.h" #include "Constants.h" -#include "IBluetoothConnector.h" #include "BluetoothWrapper.h" #include "CommandSerializer.h" #include "Exceptions.h" -#include "TimedMessageQueue.h" -#include "SingleInstanceFuture.h" -#include "CascadiaCodeFont.h" #include "Headphones.h" #include -#include class Listener { @@ -27,7 +21,7 @@ class Listener - when ack is received, set it to 1 */ public: - Listener(); + Listener(Headphones& headphones, BluetoothWrapper& bt); void listen(); inline BtMessage parse(Buffer msg); void handle_message(Buffer msg); From 99748485771986c778fae412b21e1a7376f465f5 Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Mon, 4 Sep 2023 10:44:28 +0530 Subject: [PATCH 06/15] Fixed cyclic dependency issues --- Client/BluetoothWrapper.cpp | 41 ++++++++++++++++--------------------- Client/BluetoothWrapper.h | 17 ++++++++------- Client/CMakeLists.txt | 1 + Client/CrossPlatformGUI.cpp | 5 ++--- Client/CrossPlatformGUI.h | 2 ++ Client/Headphones.cpp | 2 ++ Client/Headphones.h | 2 ++ Client/Listener.cpp | 22 ++++++++------------ Client/Listener.h | 8 +------- 9 files changed, 45 insertions(+), 55 deletions(-) diff --git a/Client/BluetoothWrapper.cpp b/Client/BluetoothWrapper.cpp index 9faa6de..cb34d99 100644 --- a/Client/BluetoothWrapper.cpp +++ b/Client/BluetoothWrapper.cpp @@ -1,11 +1,5 @@ #include "BluetoothWrapper.h" -BluetoothWrapper::BluetoothWrapper(std::unique_ptr listener, std::unique_ptr connector) -{ - this->_listener.swap(listener); - this->_connector.swap(connector); -} - BluetoothWrapper::BluetoothWrapper(std::unique_ptr connector) { this->_connector.swap(connector); @@ -13,7 +7,6 @@ BluetoothWrapper::BluetoothWrapper(std::unique_ptr connecto BluetoothWrapper::BluetoothWrapper(BluetoothWrapper&& other) noexcept { - this->_listener.swap(other._listener); this->_connector.swap(other._connector); this->_seqNumber = other._seqNumber; } @@ -29,19 +22,10 @@ BluetoothWrapper& BluetoothWrapper::operator=(BluetoothWrapper&& other) noexcept return *this; } -void BluetoothWrapper::moveListener(std::unique_ptr listener) -{ - this->_listener = std::move(listener); -} - -void BluetoothWrapper::registerListener() -{ - auto useless_future = std::async(std::launch::async, this->_listener->listen(), this->_listener.get()); -} - int BluetoothWrapper::sendCommand(const std::vector& bytes) { std::lock_guard guard(this->_connectorMtx); + std::lock_guard guard2(this->_dataMtx); auto data = CommandSerializer::packageDataForBt(bytes, DATA_TYPE::DATA_MDR, this->_seqNumber++); auto bytesSent = this->_connector->send(data.data(), data.size()); @@ -53,6 +37,7 @@ int BluetoothWrapper::sendCommand(const std::vector& bytes) int BluetoothWrapper::sendCommand(const std::vector& bytes, DATA_TYPE dtype) { std::lock_guard guard(this->_connectorMtx); + std::lock_guard guard2(this->_dataMtx); auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber++); auto bytesSent = this->_connector->send(data.data(), data.size()); @@ -88,10 +73,20 @@ std::vector BluetoothWrapper::getConnectedDevices() void BluetoothWrapper::_waitForAck() { - do{ - // Spin wait while ACK not received - // I want to change it later - } while (this->_listener->getAck() == false); + // std::lock_guard guard(this->_dataMtx); + std::unique_lock 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() @@ -135,8 +130,8 @@ Buffer BluetoothWrapper::readReplies() this->_seqNumber = msg.seqNumber; } -void BluetoothWrapper::setSeqNumber(unsigned int seqNumber) +void BluetoothWrapper::setSeqNumber(unsigned char seqNumber) { - std::lock_guard guard(this->_connectorMtx); + std::lock_guard guard(this->_dataMtx); this->_seqNumber = seqNumber; } diff --git a/Client/BluetoothWrapper.h b/Client/BluetoothWrapper.h index 7f57102..7f8bc46 100644 --- a/Client/BluetoothWrapper.h +++ b/Client/BluetoothWrapper.h @@ -1,6 +1,5 @@ #pragma once -#include "Listener.h" #include "IBluetoothConnector.h" #include "CommandSerializer.h" #include "Constants.h" @@ -15,7 +14,6 @@ class BluetoothWrapper { public: - BluetoothWrapper(std::unique_ptr listener, std::unique_ptr connector); BluetoothWrapper(std::unique_ptr connector); BluetoothWrapper(const BluetoothWrapper&) = delete; @@ -24,9 +22,6 @@ class BluetoothWrapper BluetoothWrapper(BluetoothWrapper&& other) noexcept; BluetoothWrapper& operator=(BluetoothWrapper&& other) noexcept; - void moveListener(std::unique_ptr listener); - void registerListener(); - int sendCommand(const std::vector& bytes); int sendCommand(const std::vector& bytes, DATA_TYPE dtype); @@ -38,14 +33,18 @@ class BluetoothWrapper void disconnect() noexcept; std::vector getConnectedDevices(); - void setSeqNumber(unsigned int seqNumber); + void setSeqNumber(unsigned char seqNumber); + void postAck(); private: void _waitForAck(); - Listener * _listener; std::unique_ptr _connector; - std::unique_ptr _listener; std::mutex _connectorMtx; - unsigned int _seqNumber = 0; + std::mutex _dataMtx; + unsigned char _seqNumber = 0; + unsigned int _ackBuffer = 0; + +public: + std::condition_variable _ack; }; diff --git a/Client/CMakeLists.txt b/Client/CMakeLists.txt index ae6c029..66476bc 100644 --- a/Client/CMakeLists.txt +++ b/Client/CMakeLists.txt @@ -14,6 +14,7 @@ target_sources(SonyHeadphonesClient ByteMagic.cpp CommandSerializer.cpp TimedMessageQueue.cpp + Listener.cpp CrossPlatformGUI.cpp "Headphones.cpp") target_include_directories (SonyHeadphonesClient diff --git a/Client/CrossPlatformGUI.cpp b/Client/CrossPlatformGUI.cpp index 4a83c66..1c979bd 100644 --- a/Client/CrossPlatformGUI.cpp +++ b/Client/CrossPlatformGUI.cpp @@ -123,9 +123,8 @@ void CrossPlatformGUI::_drawDeviceDiscovery() this->_connectFuture.setFromAsync([this]() { this->_bt.connect(this->_connectedDevice.mac); }); // Add post-connection setup here - std::unique_ptr listener = std::make_unique(this->_headphones, this->_bt); - this->_bt.moveListener(std::move(listener)); - this->_bt.registerListener(); + _listener = std::make_unique(this->_headphones, this->_bt); + auto useless_future = std::async(std::launch::async, &Listener::listen, this->_listener.get()); } } } diff --git a/Client/CrossPlatformGUI.h b/Client/CrossPlatformGUI.h index 1124d88..d1bfa10 100644 --- a/Client/CrossPlatformGUI.h +++ b/Client/CrossPlatformGUI.h @@ -49,6 +49,8 @@ class CrossPlatformGUI SingleInstanceFuture _connectFuture; TimedMessageQueue _mq; Headphones _headphones; + // Listener _listener; + std::unique_ptr _listener; }; diff --git a/Client/Headphones.cpp b/Client/Headphones.cpp index c4f453e..9f55662 100644 --- a/Client/Headphones.cpp +++ b/Client/Headphones.cpp @@ -229,6 +229,7 @@ void Headphones::setStateFromReply(BtMessage replyMessage) switch (cmd) { case COMMAND_TYPE::DEVICES_QUERY_RESPONSE: + { if (bytes[1] != 1) // Wrong query type, break break; @@ -265,6 +266,7 @@ void Headphones::setStateFromReply(BtMessage replyMessage) this->_dev2 = this->_savedDevices[this->_savedDevices.size()-1]; } break; + } default: break; diff --git a/Client/Headphones.h b/Client/Headphones.h index 9960fab..c1f65ee 100644 --- a/Client/Headphones.h +++ b/Client/Headphones.h @@ -1,3 +1,5 @@ +#pragma once + #include "SingleInstanceFuture.h" #include "BluetoothWrapper.h" #include "Constants.h" diff --git a/Client/Listener.cpp b/Client/Listener.cpp index ba30d34..68fcf66 100644 --- a/Client/Listener.cpp +++ b/Client/Listener.cpp @@ -2,15 +2,14 @@ Listener::Listener(Headphones& headphones, BluetoothWrapper& bt): _headphones(headphones), -_bt(bt), -_ackRecvd(false) +_bt(bt) {} void Listener::listen() { for(;;) { - Buffer reply = _bt.readReplies(); + Buffer reply = this->_bt.readReplies(); this->handle_message(reply); } } @@ -23,20 +22,17 @@ inline BtMessage Listener::parse(Buffer msg) void Listener::handle_message(Buffer msg) { BtMessage m = this->parse(msg); + this->_bt.setSeqNumber(m.seqNumber); + if (m.dataType == DATA_TYPE::ACK) { - // std::condition_variable::notify_all - std::lock_guard guard(this->_listenerMtx); - this->_ackRecvd = true; - } else if (m.dataType == DATA_TYPE::DATA_MDR || m.dataType == DATA_TYPE::DATA_MDR_NO2) { + this->_bt.postAck(); + this->_bt._ack.notify_one(); + } + else if (m.dataType == DATA_TYPE::DATA_MDR || m.dataType == DATA_TYPE::DATA_MDR_NO2) + { // Set these values as current values of Headphone property this->_headphones.setStateFromReply(m); this->_bt.sendCommand({}, DATA_TYPE::ACK); } } - -bool Listener::getAck() -{ - std::lock_guard guard(_listenerMtx); - return (this->_ackRecvd == true); -} \ No newline at end of file diff --git a/Client/Listener.h b/Client/Listener.h index b5845a0..b547bec 100644 --- a/Client/Listener.h +++ b/Client/Listener.h @@ -6,6 +6,7 @@ #include "Exceptions.h" #include "Headphones.h" +#include #include class Listener @@ -25,15 +26,8 @@ class Listener void listen(); inline BtMessage parse(Buffer msg); void handle_message(Buffer msg); - bool getAck(); private: - Headphones& _headphones; BluetoothWrapper& _bt; - - std::mutex _listenerMtx; - std::condition_variable _condt; - bool _ackRecvd; - }; \ No newline at end of file From 8639d9e6090eb86102a6cffe94b662ae68ad24a0 Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Mon, 4 Sep 2023 19:51:10 +0530 Subject: [PATCH 07/15] fixed timing issues; listener working (but barely) --- Client/BluetoothWrapper.cpp | 25 ++++++++++++++++--------- Client/CrossPlatformGUI.cpp | 17 ++++++++++------- Client/Headphones.cpp | 5 +++++ Client/Headphones.h | 3 ++- Client/Listener.cpp | 3 +++ 5 files changed, 36 insertions(+), 17 deletions(-) diff --git a/Client/BluetoothWrapper.cpp b/Client/BluetoothWrapper.cpp index cb34d99..b2b0692 100644 --- a/Client/BluetoothWrapper.cpp +++ b/Client/BluetoothWrapper.cpp @@ -1,4 +1,5 @@ #include "BluetoothWrapper.h" +#include BluetoothWrapper::BluetoothWrapper(std::unique_ptr connector) { @@ -24,10 +25,13 @@ BluetoothWrapper& BluetoothWrapper::operator=(BluetoothWrapper&& other) noexcept int BluetoothWrapper::sendCommand(const std::vector& bytes) { - std::lock_guard guard(this->_connectorMtx); - std::lock_guard guard2(this->_dataMtx); - auto data = CommandSerializer::packageDataForBt(bytes, DATA_TYPE::DATA_MDR, this->_seqNumber++); - auto bytesSent = this->_connector->send(data.data(), data.size()); + int bytesSent; + { + std::lock_guard guard(this->_connectorMtx); + std::lock_guard guard2(this->_dataMtx); + auto data = CommandSerializer::packageDataForBt(bytes, DATA_TYPE::DATA_MDR, this->_seqNumber ^ 0x01); + bytesSent = this->_connector->send(data.data(), data.size()); + } this->_waitForAck(); @@ -36,10 +40,13 @@ int BluetoothWrapper::sendCommand(const std::vector& bytes) int BluetoothWrapper::sendCommand(const std::vector& bytes, DATA_TYPE dtype) { - std::lock_guard guard(this->_connectorMtx); - std::lock_guard guard2(this->_dataMtx); - auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber++); - auto bytesSent = this->_connector->send(data.data(), data.size()); + int bytesSent; + { + std::lock_guard guard(this->_connectorMtx); + std::lock_guard guard2(this->_dataMtx); + auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber ^ 0x01); + bytesSent = this->_connector->send(data.data(), data.size()); + } if (dtype != DATA_TYPE::ACK) this->_waitForAck(); @@ -102,7 +109,7 @@ Buffer BluetoothWrapper::readReplies() size_t messageStart = 0; size_t messageEnd = numRecvd; - for (size_t i = 0; i < numRecvd; i++) + for (int i = 0; i < numRecvd; i++) { if (buf[i] == START_MARKER) { diff --git a/Client/CrossPlatformGUI.cpp b/Client/CrossPlatformGUI.cpp index 1c979bd..bda5a42 100644 --- a/Client/CrossPlatformGUI.cpp +++ b/Client/CrossPlatformGUI.cpp @@ -120,11 +120,14 @@ void CrossPlatformGUI::_drawDeviceDiscovery() if (selectedDevice != -1) { this->_connectedDevice = connectedDevices[selectedDevice]; - this->_connectFuture.setFromAsync([this]() { this->_bt.connect(this->_connectedDevice.mac); }); - - // Add post-connection setup here - _listener = std::make_unique(this->_headphones, this->_bt); - auto useless_future = std::async(std::launch::async, &Listener::listen, this->_listener.get()); + this->_connectFuture.setFromAsync([this]() { + this->_bt.connect(this->_connectedDevice.mac); + + // Add post-connection setup here + _listener = std::make_unique(this->_headphones, this->_bt); + auto useless_future = std::async(std::launch::async, &Listener::listen, this->_listener.get()); + } + ); } } } @@ -249,8 +252,8 @@ void CrossPlatformGUI::_drawSpeakToChat() enum AutoOff { Time_Short, Time_Std, Time_Long, Time_Inf, Time_count}; const char* Time_hints[Time_count] = { "Short", "Standard", "Long", "Off" }; - static bool S2Ctoggle_check = true; - static int S2C_Sens = Sens_Low; + static bool S2Ctoggle_check = false; + static int S2C_Sens = Sens_Auto; static bool S2C_Voice = 0; static int S2C_AutoOff = Time_Short; diff --git a/Client/Headphones.cpp b/Client/Headphones.cpp index 9f55662..f09edd1 100644 --- a/Client/Headphones.cpp +++ b/Client/Headphones.cpp @@ -265,6 +265,11 @@ void Headphones::setStateFromReply(BtMessage replyMessage) if (number == 2) this->_dev2 = this->_savedDevices[this->_savedDevices.size()-1]; } + std::cout<<"Connected devices received:\n"; + for (auto &dev: this->_savedDevices) + { + std::cout<< dev.name<<": "< #include template @@ -59,7 +60,7 @@ class Headphones { Property _optimizerState = { OPTIMIZER_STATE::IDLE }; - Property _speakToChat = { S2C_TOGGLE::INACTIVE }; + Property _speakToChat = { S2C_TOGGLE::INACTIVE, S2C_TOGGLE::INACTIVE }; Property _s2cOptions = { 0 }; std::vector _savedDevices; diff --git a/Client/Listener.cpp b/Client/Listener.cpp index 68fcf66..9e1c286 100644 --- a/Client/Listener.cpp +++ b/Client/Listener.cpp @@ -7,9 +7,12 @@ _bt(bt) void Listener::listen() { + std::cout << "Listener registered." << std::endl; for(;;) { Buffer reply = this->_bt.readReplies(); + if (reply[0] == static_cast(DATA_TYPE::UNKNOWN)) + continue; this->handle_message(reply); } } From b9ebbe8563f90520a8960d7bcec66506540700c6 Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Tue, 5 Sep 2023 22:13:08 +0530 Subject: [PATCH 08/15] Listener completed --- Client/BluetoothWrapper.cpp | 6 +++++- Client/CommandSerializer.cpp | 2 +- Client/CrossPlatformGUI.cpp | 6 +++--- Client/Headphones.cpp | 16 +++++++++++++--- Client/Headphones.h | 1 + Client/Listener.cpp | 2 -- Client/Listener.h | 5 ----- 7 files changed, 23 insertions(+), 15 deletions(-) diff --git a/Client/BluetoothWrapper.cpp b/Client/BluetoothWrapper.cpp index b2b0692..fe5a558 100644 --- a/Client/BluetoothWrapper.cpp +++ b/Client/BluetoothWrapper.cpp @@ -80,7 +80,6 @@ std::vector BluetoothWrapper::getConnectedDevices() void BluetoothWrapper::_waitForAck() { - // std::lock_guard guard(this->_dataMtx); std::unique_lock guard(this->_dataMtx); while (!(this->_ackBuffer > 0)){ @@ -108,6 +107,11 @@ Buffer BluetoothWrapper::readReplies() auto numRecvd = this->_connector->recv(buf, sizeof(buf)); size_t messageStart = 0; size_t messageEnd = numRecvd; + for(int i=0; i(src[5]); + unsigned int numMsgBytes = static_cast(src[5]); ret.messageBytes.insert(ret.messageBytes.end(), src.begin() + 6, src.begin() + 6 + numMsgBytes); return ret; } diff --git a/Client/CrossPlatformGUI.cpp b/Client/CrossPlatformGUI.cpp index bda5a42..6c34730 100644 --- a/Client/CrossPlatformGUI.cpp +++ b/Client/CrossPlatformGUI.cpp @@ -1,4 +1,4 @@ -#include "CrossPlatformGUI.h" +#include "CrossPlatformGUI.h" bool CrossPlatformGUI::performGUIPass() { @@ -28,7 +28,7 @@ bool CrossPlatformGUI::performGUIPass() // ImGui::Spacing(); ImGui::Separator(); this->_drawASMControls(); - if (this->_bt.isConnected() && (std::string(this->_connectedDevice.name.c_str()) == "WH-1000XM4")){ + if (this->_bt.isConnected() && (this->_connectedDevice.name) == "WH-1000XM4"){ this->_drawSpeakToChat(); this->_drawOptimizerButton(); } @@ -124,7 +124,7 @@ void CrossPlatformGUI::_drawDeviceDiscovery() this->_bt.connect(this->_connectedDevice.mac); // Add post-connection setup here - _listener = std::make_unique(this->_headphones, this->_bt); + this->_listener = std::make_unique(this->_headphones, this->_bt); auto useless_future = std::async(std::launch::async, &Listener::listen, this->_listener.get()); } ); diff --git a/Client/Headphones.cpp b/Client/Headphones.cpp index f09edd1..75b7f53 100644 --- a/Client/Headphones.cpp +++ b/Client/Headphones.cpp @@ -125,6 +125,7 @@ bool Headphones::isChanged() void Headphones::setChanges() { + std::lock_guard guard(this->_sendMtx); if (!(this->_optimizerState.isFulfilled())) { auto state = this->_optimizerState.desired; @@ -219,6 +220,14 @@ void Headphones::setChanges() this->_vptType.fulfill(); this->_surroundPosition.fulfill(); } + + + // THIS IS A WORKAROUND + // My XM4 do not respond when 2 commands of the same function are sent back to back + // This command breaks the chain and makes it respond every time + { + this->_conn.sendCommand({0x36, 0x01}, DATA_TYPE::DATA_MDR_NO2); + } } void Headphones::setStateFromReply(BtMessage replyMessage) @@ -235,7 +244,7 @@ void Headphones::setStateFromReply(BtMessage replyMessage) break; int idx = 3; - int numDevices = static_cast(bytes[2]); + int numDevices = static_cast(bytes[2]); for (; numDevices > 0; numDevices--) { std::string mac_addr = ""; @@ -246,10 +255,11 @@ void Headphones::setStateFromReply(BtMessage replyMessage) idx += MAC_ADDR_STR_SIZE; - int number = static_cast(bytes[idx]); + int number = static_cast(bytes[idx]); idx++; - int name_length = static_cast(bytes[idx]); + int name_length = static_cast(bytes[idx]); + idx++; std::string name = ""; for (int i = idx; i _savedDevices; BluetoothDevice _dev1, _dev2; std::mutex _propertyMtx; + std::mutex _sendMtx; BluetoothWrapper& _conn; }; diff --git a/Client/Listener.cpp b/Client/Listener.cpp index 9e1c286..9ba90d1 100644 --- a/Client/Listener.cpp +++ b/Client/Listener.cpp @@ -11,8 +11,6 @@ void Listener::listen() for(;;) { Buffer reply = this->_bt.readReplies(); - if (reply[0] == static_cast(DATA_TYPE::UNKNOWN)) - continue; this->handle_message(reply); } } diff --git a/Client/Listener.h b/Client/Listener.h index b547bec..4603b84 100644 --- a/Client/Listener.h +++ b/Client/Listener.h @@ -16,11 +16,6 @@ class Listener Parse messages Call message handler */ - /* - have a valid bit for ack messages - - when command is sent, it is set to 0 by locking - - when ack is received, set it to 1 - */ public: Listener(Headphones& headphones, BluetoothWrapper& bt); void listen(); From 333b269f4a383f88e6b6473bd13f8ca293da8ab3 Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Fri, 8 Sep 2023 13:52:40 +0530 Subject: [PATCH 09/15] Multi-point options added --- Client/BluetoothWrapper.cpp | 297 +++++++------- Client/CommandSerializer.cpp | 494 +++++++++++----------- Client/CommandSerializer.h | 99 ++--- Client/Constants.h | 313 +++++++------- Client/CrossPlatformGUI.cpp | 776 +++++++++++++++++++---------------- Client/CrossPlatformGUI.h | 113 ++--- Client/Headphones.cpp | 681 +++++++++++++++++------------- Client/Headphones.h | 189 +++++---- Client/Listener.cpp | 79 ++-- 9 files changed, 1630 insertions(+), 1411 deletions(-) diff --git a/Client/BluetoothWrapper.cpp b/Client/BluetoothWrapper.cpp index fe5a558..5fc1acc 100644 --- a/Client/BluetoothWrapper.cpp +++ b/Client/BluetoothWrapper.cpp @@ -1,148 +1,149 @@ -#include "BluetoothWrapper.h" -#include - -BluetoothWrapper::BluetoothWrapper(std::unique_ptr connector) -{ - this->_connector.swap(connector); -} - -BluetoothWrapper::BluetoothWrapper(BluetoothWrapper&& other) noexcept -{ - this->_connector.swap(other._connector); - this->_seqNumber = other._seqNumber; -} - -BluetoothWrapper& BluetoothWrapper::operator=(BluetoothWrapper&& other) noexcept -{ - //self assignment - if (this == &other) return *this; - - this->_connector.swap(other._connector); - this->_seqNumber = other._seqNumber; - - return *this; -} - -int BluetoothWrapper::sendCommand(const std::vector& bytes) -{ - int bytesSent; - { - std::lock_guard guard(this->_connectorMtx); - std::lock_guard guard2(this->_dataMtx); - auto data = CommandSerializer::packageDataForBt(bytes, DATA_TYPE::DATA_MDR, this->_seqNumber ^ 0x01); - bytesSent = this->_connector->send(data.data(), data.size()); - } - - this->_waitForAck(); - - return bytesSent; -} - -int BluetoothWrapper::sendCommand(const std::vector& bytes, DATA_TYPE dtype) -{ - int bytesSent; - { - std::lock_guard guard(this->_connectorMtx); - std::lock_guard guard2(this->_dataMtx); - auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber ^ 0x01); - bytesSent = this->_connector->send(data.data(), data.size()); - } - - if (dtype != DATA_TYPE::ACK) - this->_waitForAck(); - - return bytesSent; -} - -bool BluetoothWrapper::isConnected() noexcept -{ - return this->_connector->isConnected(); -} - -void BluetoothWrapper::connect(const std::string& addr) -{ - std::lock_guard guard(this->_connectorMtx); - this->_connector->connect(addr); -} - -void BluetoothWrapper::disconnect() noexcept -{ - std::lock_guard guard(this->_connectorMtx); - this->_seqNumber = 0; - this->_connector->disconnect(); -} - - -std::vector BluetoothWrapper::getConnectedDevices() -{ - return this->_connector->getConnectedDevices(); -} - -void BluetoothWrapper::_waitForAck() -{ - std::unique_lock 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; - char buf[MAX_BLUETOOTH_MESSAGE_SIZE] = { 0 }; - Buffer msgBytes; - - do - { - auto numRecvd = this->_connector->recv(buf, sizeof(buf)); - size_t messageStart = 0; - size_t messageEnd = numRecvd; - for(int i=0; i_seqNumber = msg.seqNumber; -} - -void BluetoothWrapper::setSeqNumber(unsigned char seqNumber) -{ - std::lock_guard guard(this->_dataMtx); - this->_seqNumber = seqNumber; -} +#include "BluetoothWrapper.h" +#include + +BluetoothWrapper::BluetoothWrapper(std::unique_ptr connector) +{ + this->_connector.swap(connector); +} + +BluetoothWrapper::BluetoothWrapper(BluetoothWrapper&& other) noexcept +{ + this->_connector.swap(other._connector); + this->_seqNumber = other._seqNumber; +} + +BluetoothWrapper& BluetoothWrapper::operator=(BluetoothWrapper&& other) noexcept +{ + //self assignment + if (this == &other) return *this; + + this->_connector.swap(other._connector); + this->_seqNumber = other._seqNumber; + + return *this; +} + +int BluetoothWrapper::sendCommand(const std::vector& bytes) +{ + int bytesSent; + { + std::lock_guard guard(this->_connectorMtx); + std::lock_guard guard2(this->_dataMtx); + auto data = CommandSerializer::packageDataForBt(bytes, DATA_TYPE::DATA_MDR, this->_seqNumber ^ 0x01); + bytesSent = this->_connector->send(data.data(), data.size()); + } + + this->_waitForAck(); + + return bytesSent; +} + +int BluetoothWrapper::sendCommand(const std::vector& bytes, DATA_TYPE dtype) +{ + int bytesSent; + { + std::lock_guard guard(this->_connectorMtx); + std::lock_guard guard2(this->_dataMtx); + auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber ^ 0x01); + bytesSent = this->_connector->send(data.data(), data.size()); + } + + if (dtype != DATA_TYPE::ACK) + this->_waitForAck(); + + return bytesSent; +} + +bool BluetoothWrapper::isConnected() noexcept +{ + return this->_connector->isConnected(); +} + +void BluetoothWrapper::connect(const std::string& addr) +{ + std::lock_guard guard(this->_connectorMtx); + this->_connector->connect(addr); +} + +void BluetoothWrapper::disconnect() noexcept +{ + std::lock_guard guard(this->_connectorMtx); + this->_seqNumber = 0; + this->_connector->disconnect(); +} + + +std::vector BluetoothWrapper::getConnectedDevices() +{ + return this->_connector->getConnectedDevices(); +} + +void BluetoothWrapper::_waitForAck() +{ + std::unique_lock guard(this->_dataMtx); + + while (!(this->_ackBuffer > 0)){ + this->_ack.wait(guard); + } + + std::cout<<"Consuming..."<_ackBuffer--; +} + +void BluetoothWrapper::postAck() +{ + std::lock_guard guard(this->_dataMtx); + std::cout<<"Posting..."<_ackBuffer++; +} + +Buffer BluetoothWrapper::readReplies() +{ + bool ongoingMessage = false; + bool messageFinished = false; + char buf[MAX_BLUETOOTH_MESSAGE_SIZE] = { 0 }; + Buffer msgBytes; + + do + { + auto numRecvd = this->_connector->recv(buf, sizeof(buf)); + size_t messageStart = 0; + size_t messageEnd = numRecvd; + // for(int i=0; i_seqNumber = msg.seqNumber; +} + +void BluetoothWrapper::setSeqNumber(unsigned char seqNumber) +{ + std::lock_guard guard(this->_dataMtx); + this->_seqNumber = seqNumber; +} diff --git a/Client/CommandSerializer.cpp b/Client/CommandSerializer.cpp index 99a88ec..a1cced0 100644 --- a/Client/CommandSerializer.cpp +++ b/Client/CommandSerializer.cpp @@ -1,240 +1,254 @@ -#include "CommandSerializer.h" - -/* - * Because - * 0x3E represents beginning of packet - * 0xeC 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 -{ - Buffer _escapeSpecials(const Buffer& src) - { - Buffer ret; - ret.reserve(src.size()); - - for (auto&& b : src) - { - switch (b) - { - case 60: - ret.push_back(ESCAPED_BYTE_SENTRY); - ret.push_back(ESCAPED_60); - break; - - case 61: - ret.push_back(ESCAPED_BYTE_SENTRY); - ret.push_back(ESCAPED_61); - break; - - case 62: - ret.push_back(ESCAPED_BYTE_SENTRY); - ret.push_back(ESCAPED_62); - break; - - default: - ret.push_back(b); - break; - } - } - - return ret; - } - - Buffer _unescapeSpecials(const Buffer& src) - { - Buffer ret; - ret.reserve(src.size()); - - for (size_t i = 0; i < src.size(); i++) - { - auto currByte = src[i]; - if (currByte == ESCAPED_BYTE_SENTRY) - { - if (i == src.size() - 1) - { - throw std::runtime_error("No data left for escaped byte data"); - } - i = i + 1; - switch (src[i]) - { - case ESCAPED_60: - ret.push_back(60); - break; - - case ESCAPED_61: - ret.push_back(61); - break; - - case ESCAPED_62: - ret.push_back(62); - break; - - default: - throw std::runtime_error("Unexpected escaped byte"); - break; - } - } - else - { - ret.push_back(currByte); - } - } - - return ret; - } - - unsigned char _sumChecksum(const char* src, size_t size) - { - unsigned char accumulator = 0; - for (size_t i = 0; i < size; i++) - { - accumulator += src[i]; - } - return accumulator; - } - - unsigned char _sumChecksum(const Buffer& src) - { - return _sumChecksum(src.data(), src.size()); - } - - Buffer packageDataForBt(const Buffer& src, DATA_TYPE dataType, unsigned int seqNumber) - { - //Reserve at least the size for the size, start&end markers, and the source - Buffer toEscape; - toEscape.reserve(src.size() + 2 + sizeof(int)); - Buffer ret; - ret.reserve(toEscape.capacity()); - toEscape.push_back(static_cast(dataType)); - toEscape.push_back(seqNumber); - auto retSize = intToBytesBE(static_cast(src.size())); - //Insert data size - toEscape.insert(toEscape.end(), retSize.begin(), retSize.end()); - //Insert command data - toEscape.insert(toEscape.end(), src.begin(), src.end()); - - auto checksum = _sumChecksum(toEscape); - toEscape.push_back(checksum); - toEscape = _escapeSpecials(toEscape); - - - ret.push_back(START_MARKER); - ret.insert(ret.end(), toEscape.begin(), toEscape.end()); - ret.push_back(END_MARKER); - - - // Message will be chunked if it's larger than MAX_BLUETOOTH_MESSAGE_SIZE, just crash to deal with it for now - if (ret.size() > MAX_BLUETOOTH_MESSAGE_SIZE) - { - throw std::runtime_error("Exceeded the max bluetooth message size, and I can't handle chunked messages"); - } - - return ret; - } - - BtMessage unpackBtMessage(const Buffer& src) - { - //Message data format: ESCAPE_SPECIALS(<1 BYTE CHECKSUM>) - auto unescaped = _unescapeSpecials(src); - - if (src.size() < 7) - { - throw std::runtime_error("Invalid message: Smaller than the minimum message size"); - } - - BtMessage ret; - ret.dataType = static_cast(src[0]); - ret.seqNumber = src[1]; - if ((unsigned char)src[src.size() - 1] != _sumChecksum(src.data(), src.size() - 1)) - { - throw RecoverableException("Invalid checksum!", true); - } - unsigned int numMsgBytes = static_cast(src[5]); - ret.messageBytes.insert(ret.messageBytes.end(), src.begin() + 6, src.begin() + 6 + numMsgBytes); - return ret; - } - - NC_DUAL_SINGLE_VALUE getDualSingleForAsmLevel(char asmLevel) - { - NC_DUAL_SINGLE_VALUE val = NC_DUAL_SINGLE_VALUE::OFF; - if (asmLevel > MAX_STEPS_WH_1000_XM3) - { - throw std::runtime_error("Exceeded max steps"); - } - else if (asmLevel == 1) - { - val = NC_DUAL_SINGLE_VALUE::SINGLE; - } - else if (asmLevel == 0) - { - val = NC_DUAL_SINGLE_VALUE::DUAL; - } - return val; - } - - Buffer serializeXM4OptimizeCommand(OPTIMIZER_STATE state) - { - Buffer ret; - ret.push_back(static_cast(COMMAND_TYPE::XM4_OPTIMIZER_PARAM)); - ret.push_back(static_cast(0x01)); - ret.push_back(static_cast(0x00)); - ret.push_back(static_cast(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; - ret.push_back(static_cast(COMMAND_TYPE::NCASM_SET_PARAM)); - ret.push_back(static_cast(NC_ASM_INQUIRED_TYPE::NOISE_CANCELLING_AND_AMBIENT_SOUND_MODE)); - ret.push_back(static_cast(ncAsmEffect)); - ret.push_back(static_cast(ncAsmSettingType)); - ret.push_back(static_cast(getDualSingleForAsmLevel(asmLevel))); - ret.push_back(static_cast(asmSettingType)); - ret.push_back(static_cast(asmId)); - ret.push_back(asmLevel); - return ret; - } - - Buffer serializeVPTSetting(VPT_INQUIRED_TYPE type, unsigned char preset) - { - Buffer ret; - ret.push_back(static_cast(COMMAND_TYPE::VPT_SET_PARAM)); - ret.push_back(static_cast(type)); - ret.push_back(preset); - - return ret; - } - - Buffer serializeXM4SpeakToChat(S2C_TOGGLE s2cState) - { - Buffer ret; - ret.push_back(static_cast(COMMAND_TYPE::XM4_S2C_TOGGLE_PARAM)); - ret.push_back(static_cast(0x05)); - ret.push_back(static_cast(0x01)); - ret.push_back(static_cast(s2cState)); - return ret; - } - - Buffer serializeXM4_S2C_Options(unsigned char sensitivity, unsigned char voice, unsigned char offTime) - { - Buffer ret; - ret.push_back(static_cast(COMMAND_TYPE::XM4_S2C_OPTIONS_PARAM)); - ret.push_back(static_cast(0x05)); - ret.push_back(static_cast(0x00)); - ret.push_back(static_cast(sensitivity)); - ret.push_back(static_cast(voice)); - ret.push_back(static_cast(offTime)); - return ret; - } -} - +#include "CommandSerializer.h" + +/* + * Because + * 0x3E represents beginning of packet + * 0xeC 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 +{ + Buffer _escapeSpecials(const Buffer& src) + { + Buffer ret; + ret.reserve(src.size()); + + for (auto&& b : src) + { + switch (b) + { + case 60: + ret.push_back(ESCAPED_BYTE_SENTRY); + ret.push_back(ESCAPED_60); + break; + + case 61: + ret.push_back(ESCAPED_BYTE_SENTRY); + ret.push_back(ESCAPED_61); + break; + + case 62: + ret.push_back(ESCAPED_BYTE_SENTRY); + ret.push_back(ESCAPED_62); + break; + + default: + ret.push_back(b); + break; + } + } + + return ret; + } + + Buffer _unescapeSpecials(const Buffer& src) + { + Buffer ret; + ret.reserve(src.size()); + + for (size_t i = 0; i < src.size(); i++) + { + auto currByte = src[i]; + if (currByte == ESCAPED_BYTE_SENTRY) + { + if (i == src.size() - 1) + { + throw std::runtime_error("No data left for escaped byte data"); + } + i = i + 1; + switch (src[i]) + { + case ESCAPED_60: + ret.push_back(60); + break; + + case ESCAPED_61: + ret.push_back(61); + break; + + case ESCAPED_62: + ret.push_back(62); + break; + + default: + throw std::runtime_error("Unexpected escaped byte"); + break; + } + } + else + { + ret.push_back(currByte); + } + } + + return ret; + } + + unsigned char _sumChecksum(const char* src, size_t size) + { + unsigned char accumulator = 0; + for (size_t i = 0; i < size; i++) + { + accumulator += src[i]; + } + return accumulator; + } + + unsigned char _sumChecksum(const Buffer& src) + { + return _sumChecksum(src.data(), src.size()); + } + + Buffer packageDataForBt(const Buffer& src, DATA_TYPE dataType, unsigned int seqNumber) + { + //Reserve at least the size for the size, start&end markers, and the source + Buffer toEscape; + toEscape.reserve(src.size() + 2 + sizeof(int)); + Buffer ret; + ret.reserve(toEscape.capacity()); + toEscape.push_back(static_cast(dataType)); + toEscape.push_back(seqNumber); + auto retSize = intToBytesBE(static_cast(src.size())); + //Insert data size + toEscape.insert(toEscape.end(), retSize.begin(), retSize.end()); + //Insert command data + toEscape.insert(toEscape.end(), src.begin(), src.end()); + + auto checksum = _sumChecksum(toEscape); + toEscape.push_back(checksum); + toEscape = _escapeSpecials(toEscape); + + + ret.push_back(START_MARKER); + ret.insert(ret.end(), toEscape.begin(), toEscape.end()); + ret.push_back(END_MARKER); + + + // Message will be chunked if it's larger than MAX_BLUETOOTH_MESSAGE_SIZE, just crash to deal with it for now + if (ret.size() > MAX_BLUETOOTH_MESSAGE_SIZE) + { + throw std::runtime_error("Exceeded the max bluetooth message size, and I can't handle chunked messages"); + } + + return ret; + } + + BtMessage unpackBtMessage(const Buffer& src) + { + //Message data format: ESCAPE_SPECIALS(<1 BYTE CHECKSUM>) + auto unescaped = _unescapeSpecials(src); + + if (unescaped.size() < 7) + { + throw std::runtime_error("Invalid message: Smaller than the minimum message size"); + } + + BtMessage ret; + ret.dataType = static_cast(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(unescaped[5]); + ret.messageBytes.insert(ret.messageBytes.end(), unescaped.begin() + 6, unescaped.begin() + 6 + numMsgBytes); + return ret; + } + + NC_DUAL_SINGLE_VALUE getDualSingleForAsmLevel(char asmLevel) + { + NC_DUAL_SINGLE_VALUE val = NC_DUAL_SINGLE_VALUE::OFF; + if (asmLevel > MAX_STEPS_WH_1000_XM3) + { + throw std::runtime_error("Exceeded max steps"); + } + else if (asmLevel == 1) + { + val = NC_DUAL_SINGLE_VALUE::SINGLE; + } + else if (asmLevel == 0) + { + val = NC_DUAL_SINGLE_VALUE::DUAL; + } + return val; + } + + Buffer serializeXM4OptimizeCommand(OPTIMIZER_STATE state) + { + Buffer ret; + ret.push_back(static_cast(COMMAND_TYPE::XM4_OPTIMIZER_PARAM)); + ret.push_back(static_cast(0x01)); + ret.push_back(static_cast(0x00)); + ret.push_back(static_cast(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; + ret.push_back(static_cast(COMMAND_TYPE::NCASM_SET_PARAM)); + ret.push_back(static_cast(NC_ASM_INQUIRED_TYPE::NOISE_CANCELLING_AND_AMBIENT_SOUND_MODE)); + ret.push_back(static_cast(ncAsmEffect)); + ret.push_back(static_cast(ncAsmSettingType)); + ret.push_back(static_cast(getDualSingleForAsmLevel(asmLevel))); + ret.push_back(static_cast(asmSettingType)); + ret.push_back(static_cast(asmId)); + ret.push_back(asmLevel); + return ret; + } + + Buffer serializeVPTSetting(VPT_INQUIRED_TYPE type, unsigned char preset) + { + Buffer ret; + ret.push_back(static_cast(COMMAND_TYPE::VPT_SET_PARAM)); + ret.push_back(static_cast(type)); + ret.push_back(preset); + + return ret; + } + + Buffer serializeXM4SpeakToChat(S2C_TOGGLE s2cState) + { + Buffer ret; + ret.push_back(static_cast(COMMAND_TYPE::XM4_S2C_TOGGLE_PARAM)); + ret.push_back(static_cast(0x05)); + ret.push_back(static_cast(0x01)); + ret.push_back(static_cast(s2cState)); + return ret; + } + + Buffer serializeXM4_S2C_Options(unsigned char sensitivity, unsigned char voice, unsigned char offTime) + { + Buffer ret; + ret.push_back(static_cast(COMMAND_TYPE::XM4_S2C_OPTIONS_PARAM)); + ret.push_back(static_cast(0x05)); + ret.push_back(static_cast(0x00)); + ret.push_back(static_cast(sensitivity)); + ret.push_back(static_cast(voice)); + ret.push_back(static_cast(offTime)); + return ret; + } + + Buffer serializeMultiPointCommand(MULTI_POINT_COMMANDS cmd, std::string macAddr) + { + Buffer ret; + ret.push_back(static_cast(COMMAND_TYPE::MULTI_POINT_PARAM)); + ret.push_back(static_cast(0x01)); + ret.push_back(static_cast(0x00)); + ret.push_back(static_cast(cmd)); + for (unsigned char c: macAddr) + { + ret.push_back(c); + } + return ret; + } +} + diff --git a/Client/CommandSerializer.h b/Client/CommandSerializer.h index bef7a35..73da494 100644 --- a/Client/CommandSerializer.h +++ b/Client/CommandSerializer.h @@ -1,49 +1,50 @@ -#pragma once -#include "Constants.h" -#include "ByteMagic.h" -#include -#include -#include -#include "Exceptions.h" - -constexpr int MINIMUM_VOICE_FOCUS_STEP = 2; -constexpr unsigned int ASM_LEVEL_DISABLED = -1; - -struct BtMessage -{ - DATA_TYPE dataType; - unsigned char seqNumber; - //Not really needed for now - Buffer messageBytes; -}; - - -namespace CommandSerializer -{ - //escape special chars - - Buffer _escapeSpecials(const Buffer& src); - Buffer _unescapeSpecials(const Buffer& src); - unsigned char _sumChecksum(const char* src, size_t size); - unsigned char _sumChecksum(const Buffer& src); - //Package a serialized command according to the protocol - /* - References: - * DataType - * CommandBluetoothSender.sendCommandWithRetries - * BluetoothSenderWrapper.sendCommandViaBluetooth - * - * Serialized data format: ESCAPE_SPECIALS(<1 BYTE CHECKSUM>) - */ - Buffer packageDataForBt(const Buffer& src, DATA_TYPE dataType, unsigned int seqNumber); - - 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); -} - +#pragma once +#include "Constants.h" +#include "ByteMagic.h" +#include +#include +#include +#include "Exceptions.h" + +constexpr int MINIMUM_VOICE_FOCUS_STEP = 2; +constexpr unsigned int ASM_LEVEL_DISABLED = -1; + +struct BtMessage +{ + DATA_TYPE dataType; + unsigned char seqNumber; + //Not really needed for now + Buffer messageBytes; +}; + + +namespace CommandSerializer +{ + //escape special chars + + Buffer _escapeSpecials(const Buffer& src); + Buffer _unescapeSpecials(const Buffer& src); + unsigned char _sumChecksum(const char* src, size_t size); + unsigned char _sumChecksum(const Buffer& src); + //Package a serialized command according to the protocol + /* + References: + * DataType + * CommandBluetoothSender.sendCommandWithRetries + * BluetoothSenderWrapper.sendCommandViaBluetooth + * + * Serialized data format: ESCAPE_SPECIALS(<1 BYTE CHECKSUM>) + */ + Buffer packageDataForBt(const Buffer& src, DATA_TYPE dataType, unsigned int seqNumber); + + 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); +} + diff --git a/Client/Constants.h b/Client/Constants.h index f0c61f5..d94eeb1 100644 --- a/Client/Constants.h +++ b/Client/Constants.h @@ -1,151 +1,162 @@ -#pragma once - -#include - -inline constexpr auto MAX_BLUETOOTH_MESSAGE_SIZE = 2048; -inline constexpr char START_MARKER{ 62 }; -inline constexpr char END_MARKER{ 60 }; - -inline constexpr auto MAC_ADDR_STR_SIZE = 17; - -inline constexpr auto SERVICE_UUID = "96CC203E-5068-46ad-B32D-E316F5E069BA"; -inline unsigned char SERVICE_UUID_IN_BYTES[] = { // this is the SERVICE_UUID but in bytes - 0x96, 0xcc, 0x20, 0x3e, 0x50, 0x68, 0x46, 0xad, - 0xb3, 0x2d, 0xe3, 0x16, 0xf5, 0xe0, 0x69, 0xba -}; - -#define APP_NAME "Sony Headphones App v" __HEADPHONES_APP_VERSION__ -#define APP_NAME_W (L"" APP_NAME) - -using Buffer = std::vector; - -enum class DATA_TYPE : signed char -{ - DATA = 0, - ACK = 1, - DATA_MC_NO1 = 2, - DATA_ICD = 9, - DATA_EV = 10, - DATA_MDR = 12, - DATA_COMMON = 13, - DATA_MDR_NO2 = 14, - SHOT = 16, - SHOT_MC_NO1 = 18, - SHOT_ICD = 25, - SHOT_EV = 26, - SHOT_MDR = 28, - SHOT_COMMON = 29, - SHOT_MDR_NO2 = 30, - LARGE_DATA_COMMON = 45, - UNKNOWN = -1 -}; - - -enum class NC_ASM_INQUIRED_TYPE : signed char -{ - NO_USE = 0, - NOISE_CANCELLING = 1, - NOISE_CANCELLING_AND_AMBIENT_SOUND_MODE = 2, - AMBIENT_SOUND_MODE = 3 -}; - -enum class NC_ASM_EFFECT : signed char -{ - OFF = 0, - ON = 1, - ADJUSTMENT_IN_PROGRESS = 16, - ADJUSTMENT_COMPLETION = 17 -}; - -enum class NC_ASM_SETTING_TYPE : signed char -{ - ON_OFF = 0, - LEVEL_ADJUSTMENT = 1, - DUAL_SINGLE_OFF = 2 -}; - -enum class ASM_SETTING_TYPE : signed char -{ - ON_OFF = 0, - LEVEL_ADJUSTMENT = 1 -}; - -enum class ASM_ID : signed char -{ - NORMAL = 0, - VOICE = 1 -}; - -enum class NC_DUAL_SINGLE_VALUE : signed char -{ - OFF = 0, - SINGLE = 1, - DUAL = 2 -}; - -enum class COMMAND_TYPE : signed char -{ - VPT_SET_PARAM = 72, - NCASM_SET_PARAM = 104, - XM4_OPTIMIZER_PARAM = (signed char) 0x84, - XM4_S2C_TOGGLE_PARAM = (signed char) 0xf8, - XM4_S2C_OPTIONS_PARAM = (signed char) 0xfc, - DEVICES_QUERY_RESPONSE = (signed char) 0x37, -}; - -enum class VPT_PRESET_ID : signed char -{ - OFF = 0, - OUTDOOR_FESTIVAL = 1, - ARENA = 2, - CONCERT_HALL = 3, - CLUB = 4 - //Note: Sony reserved 5~15 "for future" -}; - -enum class SOUND_POSITION_PRESET : signed char -{ - OFF = 0, - FRONT_LEFT = 1, - FRONT_RIGHT = 2, - FRONT = 3, - REAR_LEFT = 17, - REAR_RIGHT = 18, - OUT_OF_RANGE = -1 -}; - -//Needed for converting the ImGui Combo index into the VPT index. -inline const SOUND_POSITION_PRESET SOUND_POSITION_PRESET_ARRAY[] = { - SOUND_POSITION_PRESET::OFF, - SOUND_POSITION_PRESET::FRONT_LEFT, - SOUND_POSITION_PRESET::FRONT_RIGHT, - SOUND_POSITION_PRESET::FRONT, - SOUND_POSITION_PRESET::REAR_LEFT, - SOUND_POSITION_PRESET::REAR_RIGHT, - SOUND_POSITION_PRESET::OUT_OF_RANGE -}; - -enum class VPT_INQUIRED_TYPE : signed char -{ - NO_USE = 0, - VPT = 1, - 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 QUERY_RESPONSE_TYPE : signed char -{ - DEVICES = (signed char) 0x37, -}; +#pragma once + +#include + +inline constexpr auto MAX_BLUETOOTH_MESSAGE_SIZE = 2048; +inline constexpr char START_MARKER{ 62 }; +inline constexpr char END_MARKER{ 60 }; + +inline constexpr auto MAC_ADDR_STR_SIZE = 17; + +inline constexpr auto SERVICE_UUID = "96CC203E-5068-46ad-B32D-E316F5E069BA"; +inline unsigned char SERVICE_UUID_IN_BYTES[] = { // this is the SERVICE_UUID but in bytes + 0x96, 0xcc, 0x20, 0x3e, 0x50, 0x68, 0x46, 0xad, + 0xb3, 0x2d, 0xe3, 0x16, 0xf5, 0xe0, 0x69, 0xba +}; + +#define APP_NAME "Sony Headphones App v" __HEADPHONES_APP_VERSION__ +#define APP_NAME_W (L"" APP_NAME) + +using Buffer = std::vector; + +enum class DATA_TYPE : signed char +{ + DATA = 0, + ACK = 1, + DATA_MC_NO1 = 2, + DATA_ICD = 9, + DATA_EV = 10, + DATA_MDR = 12, + DATA_COMMON = 13, + DATA_MDR_NO2 = 14, + SHOT = 16, + SHOT_MC_NO1 = 18, + SHOT_ICD = 25, + SHOT_EV = 26, + SHOT_MDR = 28, + SHOT_COMMON = 29, + SHOT_MDR_NO2 = 30, + LARGE_DATA_COMMON = 45, + UNKNOWN = -1 +}; + + +enum class NC_ASM_INQUIRED_TYPE : signed char +{ + NO_USE = 0, + NOISE_CANCELLING = 1, + NOISE_CANCELLING_AND_AMBIENT_SOUND_MODE = 2, + AMBIENT_SOUND_MODE = 3 +}; + +enum class NC_ASM_EFFECT : signed char +{ + OFF = 0, + ON = 1, + ADJUSTMENT_IN_PROGRESS = 16, + ADJUSTMENT_COMPLETION = 17 +}; + +enum class NC_ASM_SETTING_TYPE : signed char +{ + ON_OFF = 0, + LEVEL_ADJUSTMENT = 1, + DUAL_SINGLE_OFF = 2 +}; + +enum class ASM_SETTING_TYPE : signed char +{ + ON_OFF = 0, + LEVEL_ADJUSTMENT = 1 +}; + +enum class ASM_ID : signed char +{ + NORMAL = 0, + VOICE = 1 +}; + +enum class NC_DUAL_SINGLE_VALUE : signed char +{ + OFF = 0, + SINGLE = 1, + DUAL = 2 +}; + +enum class COMMAND_TYPE : signed char +{ + VPT_SET_PARAM = 72, + 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, +}; + +enum class VPT_PRESET_ID : signed char +{ + OFF = 0, + OUTDOOR_FESTIVAL = 1, + ARENA = 2, + CONCERT_HALL = 3, + CLUB = 4 + //Note: Sony reserved 5~15 "for future" +}; + +enum class SOUND_POSITION_PRESET : signed char +{ + OFF = 0, + FRONT_LEFT = 1, + FRONT_RIGHT = 2, + FRONT = 3, + REAR_LEFT = 17, + REAR_RIGHT = 18, + OUT_OF_RANGE = -1 +}; + +//Needed for converting the ImGui Combo index into the VPT index. +inline const SOUND_POSITION_PRESET SOUND_POSITION_PRESET_ARRAY[] = { + SOUND_POSITION_PRESET::OFF, + SOUND_POSITION_PRESET::FRONT_LEFT, + SOUND_POSITION_PRESET::FRONT_RIGHT, + SOUND_POSITION_PRESET::FRONT, + SOUND_POSITION_PRESET::REAR_LEFT, + SOUND_POSITION_PRESET::REAR_RIGHT, + SOUND_POSITION_PRESET::OUT_OF_RANGE +}; + +enum class VPT_INQUIRED_TYPE : signed char +{ + NO_USE = 0, + VPT = 1, + 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 QUERY_RESPONSE_TYPE : signed char +{ + DEVICES = (signed char) 0x37, +}; + +enum class MULTI_POINT_COMMANDS : signed char +{ + CONNECT = (signed char) 0x01, + DISCONNECT = (signed char) 0x00 +}; diff --git a/Client/CrossPlatformGUI.cpp b/Client/CrossPlatformGUI.cpp index 6c34730..5330a07 100644 --- a/Client/CrossPlatformGUI.cpp +++ b/Client/CrossPlatformGUI.cpp @@ -1,350 +1,426 @@ -#include "CrossPlatformGUI.h" - -bool CrossPlatformGUI::performGUIPass() -{ - ImGui::NewFrame(); - - static bool isConnected = false; - - bool open = true; - - ImGui::SetNextWindowPos({ 0,0 }); - - { - //TODO: Figure out how to get rid of the Windows window, make everything transparent, and just use ImGui for everything. - //TODO: ImGuiWindowFlags_AlwaysAutoResize causes some flickering. Figure out how to stop it - ImGui::Begin("Sony Headphones", &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar); - - //Legal disclaimer - ImGui::Text("! This product is not affiliated with Sony. Use at your own risk. !"); - ImGui::Text("Source: https://github.com/Plutoberth/SonyHeadphonesClient"); - ImGui::Spacing(); - - this->_drawErrors(); - this->_drawDeviceDiscovery(); - - if (this->_bt.isConnected()) - { - // ImGui::Spacing(); - ImGui::Separator(); - this->_drawASMControls(); - if (this->_bt.isConnected() && (this->_connectedDevice.name) == "WH-1000XM4"){ - this->_drawSpeakToChat(); - this->_drawOptimizerButton(); - } - else { - this->_drawSurroundControls(); - } - - this->_setHeadphoneSettings(); - } - } - - ImGui::End(); - ImGui::Render(); - - return open; -} - -void CrossPlatformGUI::_drawErrors() -{ - //There's a slight race condition here but I don't care, it'd only be for one frame. - if (this->_mq.begin() != this->_mq.end()) - { - ImGui::Text("Errors:"); - ImGui::Spacing(); - - for (auto&& message : this->_mq) - { - ImGui::Text(message.message.c_str()); - } - - ImGui::Spacing(); - } -} - -void CrossPlatformGUI::_drawDeviceDiscovery() -{ - if (ImGui::CollapsingHeader("Device Discovery ", ImGuiTreeNodeFlags_DefaultOpen)) - { - static std::vector connectedDevices; - static int selectedDevice = -1; - - if (this->_bt.isConnected()) - { - ImGui::Text("Connected to %s", this->_connectedDevice.name.c_str()); - if (ImGui::Button("Disconnect")) - { - selectedDevice = -1; - this->_bt.disconnect(); - } - } - else - { - ImGui::Text("Select from one of the available devices: "); - - int temp = 0; - for (const auto& device : connectedDevices) - { - ImGui::RadioButton(device.name.c_str(), &selectedDevice, temp++); - } - - ImGui::Spacing(); - - if (this->_connectFuture.valid()) - { - if (this->_connectFuture.ready()) - { - try - { - this->_connectFuture.get(); - } - catch (const RecoverableException& exc) - { - if (exc.shouldDisconnect) - { - this->_bt.disconnect(); - } - this->_mq.addMessage(exc.what()); - } - } - else - { - ImGui::Text("Connecting %c", "|/-\\"[(int)(ImGui::GetTime() / 0.05f) & 3]); - } - } - else - { - if (ImGui::Button("Connect")) - { - if (selectedDevice != -1) - { - this->_connectedDevice = connectedDevices[selectedDevice]; - this->_connectFuture.setFromAsync([this]() { - this->_bt.connect(this->_connectedDevice.mac); - - // Add post-connection setup here - this->_listener = std::make_unique(this->_headphones, this->_bt); - auto useless_future = std::async(std::launch::async, &Listener::listen, this->_listener.get()); - } - ); - } - } - } - - ImGui::SameLine(); - - if (this->_connectedDevicesFuture.valid()) - { - if (this->_connectedDevicesFuture.ready()) - { - try - { - connectedDevices = this->_connectedDevicesFuture.get(); - } - catch (const RecoverableException& exc) - { - if (exc.shouldDisconnect) - { - this->_bt.disconnect(); - } - this->_mq.addMessage(exc.what()); - } - } - else - { - ImGui::Text("Discovering Devices %c", "|/-\\"[(int)(ImGui::GetTime() / 0.05f) & 3]); - } - } - else - { - if (ImGui::Button("Refresh devices")) - { - selectedDevice = -1; - this->_connectedDevicesFuture.setFromAsync([this]() { return this->_bt.getConnectedDevices(); }); - } - } - } - } -} - -void CrossPlatformGUI::_drawASMControls() -{ - static bool ambientSoundControl = true; - static bool focusOnVoice = false; - static int asmLevel = 0; - - if (ImGui::CollapsingHeader("Ambient Sound Mode ", ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::Checkbox("Ambient Sound Control", &ambientSoundControl); - - if (this->_headphones.isSetAsmLevelAvailable()) - { - ImGui::Text("Control ambient sound for your %ss", this->_connectedDevice.name.c_str()); - - ImGui::SliderInt("Ambient Sound Level", &asmLevel, 0, 19); - - if (this->_headphones.isFocusOnVoiceAvailable()) - { - ImGui::Checkbox("Focus on Voice", &focusOnVoice); - } - else - { - ImGui::Text("Focus on Voice isn't enabled on this level."); - } - } - - this->_headphones.setAmbientSoundControl(ambientSoundControl); - this->_headphones.setAsmLevel(asmLevel); - this->_headphones.setFocusOnVoice(focusOnVoice); - } -} - -void CrossPlatformGUI::_drawSurroundControls() -{ - static int soundPosition = 0; - static int vptType = 0; - - if (ImGui::CollapsingHeader("Virtual Sound", ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::Text("Only one of the options may be used at a time"); - - if (ImGui::Combo("Sound Position", &soundPosition, "Off\0Front Left\0Front Right\0" - "Front\0Rear Left\0Rear Right\0\0")) - { - vptType = 0; - } - - if (ImGui::Combo("Surround (VPT)", &vptType, "Off\0Outdoor Festival\0Arena\0" - "Concert Hall\0Club\0\0")) - { - soundPosition = 0; - } - - this->_headphones.setSurroundPosition(SOUND_POSITION_PRESET_ARRAY[soundPosition]); - this->_headphones.setVptType(vptType); - } -} - -void CrossPlatformGUI::_drawOptimizerButton() -{ - if (this->_headphones.getOptimizerState() == OPTIMIZER_STATE::IDLE) - { - if (ImGui::Button("Optimize")) - { - this->_headphones.setOptimizerState(OPTIMIZER_STATE::OPTIMIZING); - } - } - else { - // TODO: Change button to show cancel option while optimizing state - if (ImGui::Button("Optimize")) - { - this->_headphones.setOptimizerState(OPTIMIZER_STATE::IDLE); - } - } -} - -void CrossPlatformGUI::_drawSpeakToChat() -{ - enum Sensitivity { Sens_Auto, Sens_High, Sens_Low, Sens_count}; - const char* Sens_hints[Sens_count] = { "Auto", "High", "Low" }; - - enum AutoOff { Time_Short, Time_Std, Time_Long, Time_Inf, Time_count}; - const char* Time_hints[Time_count] = { "Short", "Standard", "Long", "Off" }; - - static bool S2Ctoggle_check = false; - static int S2C_Sens = Sens_Auto; - static bool S2C_Voice = 0; - static int S2C_AutoOff = Time_Short; - - const char* Sens_name = (S2C_Sens >= 0 && S2C_Sens < Sens_count) ? Sens_hints[S2C_Sens] : "Unknown"; - const char* Time_name = (S2C_AutoOff >= 0 && S2C_AutoOff < Time_count) ? Time_hints[S2C_AutoOff] : "Unknown"; - // S2Ctoggle = (this->_headphones.getS2CToggle() == S2C_TOGGLE::ACTIVE) ? true : false; - // S2C_Sens = (this->_headphones.getS2COptions() & 0x00ff0000)>>16; - // S2C_Voice = (this->_headphones.getS2COptions() & 0x0000ff00)>>8; - // S2C_AutoOff = (this->_headphones.getS2COptions() & 0x000000ff); - - if (ImGui::CollapsingHeader("Speak To Chat Controls", ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::Checkbox("Speak To Chat", &S2Ctoggle_check); - - if (S2Ctoggle_check) - { - if (S2Ctoggle_check){ - // ImGui::SameLine(); - ImGui::SliderInt("Speak to chat sensitivity", &S2C_Sens, 0, Sens_count - 1, Sens_name); - ImGui::SliderInt("Speak to chat Auto close", &S2C_AutoOff, 0, Time_count - 1, Time_name); - ImGui::Checkbox("Voice passthrough", &S2C_Voice); - } - else { - } - } - } - - if (S2Ctoggle_check) - this->_headphones.setS2CToggle(S2C_TOGGLE::ACTIVE); - else - this->_headphones.setS2CToggle(S2C_TOGGLE::INACTIVE); - - this->_headphones.setS2COptions(S2C_Sens, S2C_Voice, S2C_AutoOff); - -} - -void CrossPlatformGUI::_setHeadphoneSettings() { - //Don't show if the command only takes a few frames to send - static int commandLinger = 0; - - if (this->_sendCommandFuture.ready()) - { - commandLinger = 0; - try - { - this->_sendCommandFuture.get(); - } - catch (const RecoverableException& exc) - { - std::string excString; - //We kinda have to do it here and not in the wrapper, due to async causing timing issues. To fix it, the messagequeue can be made - //static, but I'm not sure if I wanna do that. - if (exc.shouldDisconnect) - { - this->_bt.disconnect(); - excString = "Disconnected due to: "; - } - this->_mq.addMessage(excString + exc.what()); - } - } - //This means that we're waiting - else if (this->_sendCommandFuture.valid()) - { - if (commandLinger++ > (FPS / 10)) - { - ImGui::Text("Sending command %c", "|/-\\"[(int)(ImGui::GetTime() / 0.05f) & 3]); - } - } - //We're not waiting, and there's no command in the air, so we can evaluate sending a new command - else if (this->_headphones.isChanged()) - { - this->_sendCommandFuture.setFromAsync([=, this]() { - return this->_headphones.setChanges(); - }); - } -} - -CrossPlatformGUI::CrossPlatformGUI(BluetoothWrapper bt) : _bt(std::move(bt)), _headphones(_bt) -{ - // Setup Dear ImGui style - ImGui::StyleColorsDark(); - ImGuiIO& io = ImGui::GetIO(); - this->_mq = TimedMessageQueue(GUI_MAX_MESSAGES); - this->_connectedDevicesFuture.setFromAsync([this]() { return this->_bt.getConnectedDevices(); }); - - io.IniFilename = nullptr; - io.WantSaveIniSettings = false; - - //AddFontFromMemory will own the pointer, so there's no leak - char* fileData = new char[sizeof(CascadiaCodeTTF)]; - memcpy(fileData, CascadiaCodeTTF, sizeof(CascadiaCodeTTF)); - ImFont* font = io.Fonts->AddFontFromMemoryTTF(reinterpret_cast(fileData), sizeof(CascadiaCodeTTF), FONT_SIZE); - IM_ASSERT(font != NULL); -} +#include "CrossPlatformGUI.h" + +bool CrossPlatformGUI::performGUIPass() +{ + ImGui::NewFrame(); + + static bool isConnected = false; + + bool open = true; + + ImGui::SetNextWindowPos({ 0,0 }); + + { + //TODO: Figure out how to get rid of the Windows window, make everything transparent, and just use ImGui for everything. + //TODO: ImGuiWindowFlags_AlwaysAutoResize causes some flickering. Figure out how to stop it + ImGui::Begin("Sony Headphones", &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar); + + //Legal disclaimer + ImGui::Text("! This product is not affiliated with Sony. Use at your own risk. !"); + ImGui::Text("Source: https://github.com/Plutoberth/SonyHeadphonesClient"); + ImGui::Spacing(); + + this->_drawErrors(); + this->_drawDeviceDiscovery(); + + if (this->_bt.isConnected()) + { + // ImGui::Spacing(); + ImGui::Separator(); + this->_drawASMControls(); + if (this->_bt.isConnected() && (this->_connectedDevice.name) == "WH-1000XM4"){ + this->_drawSpeakToChat(); + if (this->_headphones.getMultiPointSetting()) + this->_drawMultiPointConn(); + this->_drawOptimizerButton(); + } + else { + this->_drawSurroundControls(); + } + + this->_setHeadphoneSettings(); + } + } + + ImGui::End(); + ImGui::Render(); + + return open; +} + +void CrossPlatformGUI::_drawErrors() +{ + //There's a slight race condition here but I don't care, it'd only be for one frame. + if (this->_mq.begin() != this->_mq.end()) + { + ImGui::Text("Errors:"); + ImGui::Spacing(); + + for (auto&& message : this->_mq) + { + ImGui::Text(message.message.c_str()); + } + + ImGui::Spacing(); + } +} + +void CrossPlatformGUI::_drawDeviceDiscovery() +{ + if (ImGui::CollapsingHeader("Device Discovery ", ImGuiTreeNodeFlags_DefaultOpen)) + { + static std::vector connectedDevices; + static int selectedDevice = -1; + + if (this->_bt.isConnected()) + { + ImGui::Text("Connected to %s", this->_connectedDevice.name.c_str()); + if (ImGui::Button("Disconnect")) + { + selectedDevice = -1; + this->_bt.disconnect(); + } + } + else + { + ImGui::Text("Select from one of the available devices: "); + + int temp = 0; + for (const auto& device : connectedDevices) + { + ImGui::RadioButton(device.name.c_str(), &selectedDevice, temp++); + } + + ImGui::Spacing(); + + if (this->_connectFuture.valid()) + { + if (this->_connectFuture.ready()) + { + try + { + this->_connectFuture.get(); + } + catch (const RecoverableException& exc) + { + if (exc.shouldDisconnect) + { + this->_bt.disconnect(); + } + this->_mq.addMessage(exc.what()); + } + } + else + { + ImGui::Text("Connecting %c", "|/-\\"[(int)(ImGui::GetTime() / 0.05f) & 3]); + } + } + else + { + if (ImGui::Button("Connect")) + { + if (selectedDevice != -1) + { + this->_connectedDevice = connectedDevices[selectedDevice]; + this->_connectFuture.setFromAsync([this]() { + this->_bt.connect(this->_connectedDevice.mac); + + // Add post-connection setup here + this->_listener = std::make_unique(this->_headphones, this->_bt); + auto useless_future = std::async(std::launch::async, &Listener::listen, this->_listener.get()); + } + ); + } + } + } + + ImGui::SameLine(); + + if (this->_connectedDevicesFuture.valid()) + { + if (this->_connectedDevicesFuture.ready()) + { + try + { + connectedDevices = this->_connectedDevicesFuture.get(); + } + catch (const RecoverableException& exc) + { + if (exc.shouldDisconnect) + { + this->_bt.disconnect(); + } + this->_mq.addMessage(exc.what()); + } + } + else + { + ImGui::Text("Discovering Devices %c", "|/-\\"[(int)(ImGui::GetTime() / 0.05f) & 3]); + } + } + else + { + if (ImGui::Button("Refresh devices")) + { + selectedDevice = -1; + this->_connectedDevicesFuture.setFromAsync([this]() { return this->_bt.getConnectedDevices(); }); + } + } + } + } +} + +void CrossPlatformGUI::_drawASMControls() +{ + static bool ambientSoundControl = true; + static bool focusOnVoice = false; + static int asmLevel = 0; + + if (ImGui::CollapsingHeader("Ambient Sound Mode ", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("Ambient Sound Control", &ambientSoundControl); + + if (this->_headphones.isSetAsmLevelAvailable()) + { + ImGui::Text("Control ambient sound for your %ss", this->_connectedDevice.name.c_str()); + + ImGui::SliderInt("Ambient Sound Level", &asmLevel, 0, 19); + + if (this->_headphones.isFocusOnVoiceAvailable()) + { + ImGui::Checkbox("Focus on Voice", &focusOnVoice); + } + else + { + ImGui::Text("Focus on Voice isn't enabled on this level."); + } + } + + this->_headphones.setAmbientSoundControl(ambientSoundControl); + this->_headphones.setAsmLevel(asmLevel); + this->_headphones.setFocusOnVoice(focusOnVoice); + } +} + +void CrossPlatformGUI::_drawSurroundControls() +{ + static int soundPosition = 0; + static int vptType = 0; + + if (ImGui::CollapsingHeader("Virtual Sound", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Text("Only one of the options may be used at a time"); + + if (ImGui::Combo("Sound Position", &soundPosition, "Off\0Front Left\0Front Right\0" + "Front\0Rear Left\0Rear Right\0\0")) + { + vptType = 0; + } + + if (ImGui::Combo("Surround (VPT)", &vptType, "Off\0Outdoor Festival\0Arena\0" + "Concert Hall\0Club\0\0")) + { + soundPosition = 0; + } + + this->_headphones.setSurroundPosition(SOUND_POSITION_PRESET_ARRAY[soundPosition]); + this->_headphones.setVptType(vptType); + } +} + +void CrossPlatformGUI::_drawOptimizerButton() +{ + if (this->_headphones.getOptimizerState() == OPTIMIZER_STATE::IDLE) + { + if (ImGui::Button("Optimize")) + { + this->_headphones.setOptimizerState(OPTIMIZER_STATE::OPTIMIZING); + } + } + else { + // TODO: Change button to show cancel option while optimizing state + if (ImGui::Button("Optimizing... (Press To Cancel)")) + { + this->_headphones.setOptimizerState(OPTIMIZER_STATE::IDLE); + } + } +} + +void CrossPlatformGUI::_drawSpeakToChat() +{ + enum Sensitivity { Sens_Auto, Sens_High, Sens_Low, Sens_count}; + const char* Sens_hints[Sens_count] = { "Auto", "High", "Low" }; + + enum AutoOff { Time_Short, Time_Std, Time_Long, Time_Inf, Time_count}; + const char* Time_hints[Time_count] = { "Short", "Standard", "Long", "Off" }; + + static bool S2Ctoggle_check = false; + static int S2C_Sens = Sens_Auto; + static bool S2C_Voice = 0; + static int S2C_AutoOff = Time_Short; + + const char* Sens_name = (S2C_Sens >= 0 && S2C_Sens < Sens_count) ? Sens_hints[S2C_Sens] : "Unknown"; + const char* Time_name = (S2C_AutoOff >= 0 && S2C_AutoOff < Time_count) ? Time_hints[S2C_AutoOff] : "Unknown"; + + if (ImGui::CollapsingHeader("Speak To Chat Controls", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("Speak To Chat", &S2Ctoggle_check); + + if (S2Ctoggle_check) + { + if (S2Ctoggle_check){ + // ImGui::SameLine(); + ImGui::SliderInt("Speak to chat sensitivity", &S2C_Sens, 0, Sens_count - 1, Sens_name); + ImGui::SliderInt("Speak to chat Auto close", &S2C_AutoOff, 0, Time_count - 1, Time_name); + ImGui::Checkbox("Voice passthrough", &S2C_Voice); + } + else { + } + } + } + + if (S2Ctoggle_check) + this->_headphones.setS2CToggle(S2C_TOGGLE::ACTIVE); + else + this->_headphones.setS2CToggle(S2C_TOGGLE::INACTIVE); + + this->_headphones.setS2COptions(S2C_Sens, S2C_Voice, S2C_AutoOff); + +} + +void CrossPlatformGUI::_drawMultiPointConn() +{ + auto devices = this->_headphones.getDevices(); + auto connectedDevices = this->_headphones.getConnectedDevices(); + + int dev1 = connectedDevices.first; + int dev2 = connectedDevices.second; + int old_dev1 = dev1; + int old_dev2 = dev2; + + if(ImGui::CollapsingHeader("Connected Devices", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (devices[dev1].name!="") + { + ImGui::Text(devices[dev1].name.c_str()); + ImGui::SameLine(); + if (ImGui::Button("Disconnect device 1")) + { + std::cout << "Button 1 pressed "<_headphones.setMultiPointConnection(1, 0, dev1); + } + } + + if (devices[dev1].name=="" || devices[dev2].name=="") + { + // const char* combo_preview_value = devices[dev1].name // &(devices[dev1].name); + if (ImGui::BeginCombo("Connect", "")) + { + for (int i = 0; i < devices.size(); i++) + { + if (i==dev2 || i==dev1) + continue; + + const bool is_selected = false; + if (ImGui::Selectable(devices[i].name.c_str(), is_selected)) + { + if (devices[dev1].name=="") + { + dev1 = i; + this->_headphones.setMultiPointConnection(1, dev1, 0); + } + else { + dev2 = i; + this->_headphones.setMultiPointConnection(2, dev2, 0); + } + } + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + ImGui::SameLine(); + if (devices[dev1].name=="") + ImGui::Text("Device 1"); + else + ImGui::Text("Device 2"); + } + + if (devices[dev2].name!="") + { + ImGui::Text(devices[dev2].name.c_str()); + ImGui::SameLine(); + if (ImGui::Button("Disconnect device 2")) + { + std::cout << "Button 2 pressed "<_headphones.setMultiPointConnection(2,0,dev2); + this->_headphones.setMultiPointConnection(2,0,dev2); + this->_headphones.setMultiPointConnection(2,0,dev2); + } + } + + } + +} + +void CrossPlatformGUI::_setHeadphoneSettings() { + //Don't show if the command only takes a few frames to send + static int commandLinger = 0; + + if (this->_sendCommandFuture.ready()) + { + commandLinger = 0; + try + { + this->_sendCommandFuture.get(); + } + catch (const RecoverableException& exc) + { + std::string excString; + //We kinda have to do it here and not in the wrapper, due to async causing timing issues. To fix it, the messagequeue can be made + //static, but I'm not sure if I wanna do that. + if (exc.shouldDisconnect) + { + this->_bt.disconnect(); + excString = "Disconnected due to: "; + } + this->_mq.addMessage(excString + exc.what()); + } + } + //This means that we're waiting + else if (this->_sendCommandFuture.valid()) + { + if (commandLinger++ > (FPS / 10)) + { + ImGui::Text("Sending command %c", "|/-\\"[(int)(ImGui::GetTime() / 0.05f) & 3]); + } + } + //We're not waiting, and there's no command in the air, so we can evaluate sending a new command + else if (this->_headphones.isChanged()) + { + this->_sendCommandFuture.setFromAsync([=, this]() { + return this->_headphones.setChanges(); + }); + } +} + +CrossPlatformGUI::CrossPlatformGUI(BluetoothWrapper bt) : _bt(std::move(bt)), _headphones(_bt) +{ + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + ImGuiIO& io = ImGui::GetIO(); + this->_mq = TimedMessageQueue(GUI_MAX_MESSAGES); + this->_connectedDevicesFuture.setFromAsync([this]() { return this->_bt.getConnectedDevices(); }); + + io.IniFilename = nullptr; + io.WantSaveIniSettings = false; + + //AddFontFromMemory will own the pointer, so there's no leak + char* fileData = new char[sizeof(CascadiaCodeTTF)]; + memcpy(fileData, CascadiaCodeTTF, sizeof(CascadiaCodeTTF)); + ImFont* font = io.Fonts->AddFontFromMemoryTTF(reinterpret_cast(fileData), sizeof(CascadiaCodeTTF), FONT_SIZE); + IM_ASSERT(font != NULL); +} diff --git a/Client/CrossPlatformGUI.h b/Client/CrossPlatformGUI.h index d1bfa10..928d3e0 100644 --- a/Client/CrossPlatformGUI.h +++ b/Client/CrossPlatformGUI.h @@ -1,56 +1,57 @@ -#pragma once - -#include "imgui/imgui.h" -#include "Constants.h" -#include "IBluetoothConnector.h" -#include "BluetoothWrapper.h" -#include "CommandSerializer.h" -#include "Exceptions.h" -#include "TimedMessageQueue.h" -#include "SingleInstanceFuture.h" -#include "CascadiaCodeFont.h" -#include "Headphones.h" -#include "Listener.h" - -#include -#include -#include - -constexpr auto GUI_MAX_MESSAGES = 5; -constexpr auto GUI_HEIGHT = 380; -constexpr auto GUI_WIDTH = 540; -constexpr auto FPS = 60; -constexpr auto MS_PER_FRAME = 1000 / FPS; -constexpr auto FONT_SIZE = 15.0f; -const auto WINDOW_COLOR = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - -//This class should be constructed after AFTER the Dear ImGUI context is initialized. -class CrossPlatformGUI -{ -public: - CrossPlatformGUI(BluetoothWrapper bt); - - //Run the GUI code once. This function should be called from a loop from one of the GUI impls (Windows, OSX, Linux...) - //O: true if the user wants to close the window - bool performGUIPass(); -private: - void _drawErrors(); - void _drawDeviceDiscovery(); - void _drawASMControls(); - void _drawSurroundControls(); - void _setHeadphoneSettings(); - void _drawOptimizerButton(); - void _drawSpeakToChat(); - - BluetoothDevice _connectedDevice; - BluetoothWrapper _bt; - SingleInstanceFuture> _connectedDevicesFuture; - SingleInstanceFuture _sendCommandFuture; - SingleInstanceFuture _connectFuture; - TimedMessageQueue _mq; - Headphones _headphones; - // Listener _listener; - std::unique_ptr _listener; -}; - - +#pragma once + +#include "imgui/imgui.h" +#include "Constants.h" +#include "IBluetoothConnector.h" +#include "BluetoothWrapper.h" +#include "CommandSerializer.h" +#include "Exceptions.h" +#include "TimedMessageQueue.h" +#include "SingleInstanceFuture.h" +#include "CascadiaCodeFont.h" +#include "Headphones.h" +#include "Listener.h" + +#include +#include +#include + +constexpr auto GUI_MAX_MESSAGES = 5; +constexpr auto GUI_HEIGHT = 380; +constexpr auto GUI_WIDTH = 540; +constexpr auto FPS = 60; +constexpr auto MS_PER_FRAME = 1000 / FPS; +constexpr auto FONT_SIZE = 15.0f; +const auto WINDOW_COLOR = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + +//This class should be constructed after AFTER the Dear ImGUI context is initialized. +class CrossPlatformGUI +{ +public: + CrossPlatformGUI(BluetoothWrapper bt); + + //Run the GUI code once. This function should be called from a loop from one of the GUI impls (Windows, OSX, Linux...) + //O: true if the user wants to close the window + bool performGUIPass(); +private: + void _drawErrors(); + void _drawDeviceDiscovery(); + void _drawASMControls(); + void _drawSurroundControls(); + void _setHeadphoneSettings(); + void _drawOptimizerButton(); + void _drawSpeakToChat(); + void _drawMultiPointConn(); + + BluetoothDevice _connectedDevice; + BluetoothWrapper _bt; + SingleInstanceFuture> _connectedDevicesFuture; + SingleInstanceFuture _sendCommandFuture; + SingleInstanceFuture _connectFuture; + TimedMessageQueue _mq; + Headphones _headphones; + // Listener _listener; + std::unique_ptr _listener; +}; + + diff --git a/Client/Headphones.cpp b/Client/Headphones.cpp index 75b7f53..e045007 100644 --- a/Client/Headphones.cpp +++ b/Client/Headphones.cpp @@ -1,289 +1,392 @@ -#include "Headphones.h" -#include "CommandSerializer.h" - -#include - -Headphones::Headphones(BluetoothWrapper& conn) : -_conn(conn), -_dev1({"No device", ""}), -_dev2({"No device", ""}) -{ -} - -void Headphones::setAmbientSoundControl(bool val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_ambientSoundControl.desired = val; -} - -bool Headphones::getAmbientSoundControl() -{ - return this->_ambientSoundControl.current; -} - -bool Headphones::isFocusOnVoiceAvailable() -{ - return this->_ambientSoundControl.current && this->_asmLevel.current > MINIMUM_VOICE_FOCUS_STEP; -} - -void Headphones::setFocusOnVoice(bool val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_focusOnVoice.desired = val; -} - -bool Headphones::getFocusOnVoice() -{ - return this->_focusOnVoice.current; -} - -bool Headphones::isSetAsmLevelAvailable() -{ - return this->_ambientSoundControl.current; -} - -void Headphones::setAsmLevel(int val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_asmLevel.desired = val; -} - -int Headphones::getAsmLevel() -{ - return this->_asmLevel.current; -} - -void Headphones::setOptimizerState(OPTIMIZER_STATE val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_optimizerState.desired = val; -} - -OPTIMIZER_STATE Headphones::getOptimizerState() -{ - return this->_optimizerState.current; -} - -void Headphones::setS2CToggle(S2C_TOGGLE val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_speakToChat.desired = val; -} - -void Headphones::setS2COptions(int sensitivity, bool voice, int offTime) -{ - unsigned int sens = (unsigned int) sensitivity; - unsigned int time = (unsigned int) offTime; - std::lock_guard guard(this->_propertyMtx); - this->_s2cOptions.desired = { (sens << 16) | (voice << 8) | (time) }; -} - -S2C_TOGGLE Headphones::getS2CToggle() -{ - return this->_speakToChat.current; -} - -unsigned int Headphones::getS2COptions() -{ - return this->_s2cOptions.current; -} - -void Headphones::setSurroundPosition(SOUND_POSITION_PRESET val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_surroundPosition.desired = val; -} - -SOUND_POSITION_PRESET Headphones::getSurroundPosition() -{ - return this->_surroundPosition.current; -} - -void Headphones::setVptType(int val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_vptType.desired = val; -} - -int Headphones::getVptType() -{ - return this->_vptType.current; -} - -bool Headphones::isChanged() -{ - return !(this->_ambientSoundControl.isFulfilled() && - this->_asmLevel.isFulfilled() && - this->_focusOnVoice.isFulfilled() && - this->_surroundPosition.isFulfilled() && - this->_vptType.isFulfilled() && - this->_optimizerState.isFulfilled() && - this->_speakToChat.isFulfilled() && - this->_s2cOptions.isFulfilled() - ); -} - -void Headphones::setChanges() -{ - std::lock_guard guard(this->_sendMtx); - if (!(this->_optimizerState.isFulfilled())) - { - auto state = this->_optimizerState.desired; - - this->_conn.sendCommand(CommandSerializer::serializeXM4OptimizeCommand( - state - )); - - std::lock_guard guard(this->_propertyMtx); - this->_optimizerState.fulfill(); - } - - if (!(this->_speakToChat.isFulfilled())) - { - auto s2cState = this->_speakToChat.desired; - - this->_conn.sendCommand(CommandSerializer::serializeXM4SpeakToChat( - s2cState - )); - - std::lock_guard guard(this->_propertyMtx); - this->_speakToChat.fulfill(); - } - - if (!(this->_s2cOptions.isFulfilled())) - { - auto s2cOptions = this->_s2cOptions.desired; - unsigned char sensitivity = (unsigned char) (s2cOptions >> 16) & 0xff; - unsigned char voice = (unsigned char) (s2cOptions >> 8) & 0xff; - unsigned char offTime = (unsigned char) (s2cOptions) & 0xff; - - this->_conn.sendCommand(CommandSerializer::serializeXM4_S2C_Options( - sensitivity, - voice, - offTime - )); - - std::lock_guard guard(this->_propertyMtx); - this->_s2cOptions.fulfill(); - } - - if (!(this->_ambientSoundControl.isFulfilled() && this->_focusOnVoice.isFulfilled() && this->_asmLevel.isFulfilled())) - { - auto ncAsmEffect = this->_ambientSoundControl.desired ? NC_ASM_EFFECT::ADJUSTMENT_COMPLETION : NC_ASM_EFFECT::OFF; - auto asmId = this->_focusOnVoice.desired ? ASM_ID::VOICE : ASM_ID::NORMAL; - auto asmLevel = this->_ambientSoundControl.desired ? this->_asmLevel.desired : ASM_LEVEL_DISABLED; - - this->_conn.sendCommand(CommandSerializer::serializeNcAndAsmSetting( - ncAsmEffect, - NC_ASM_SETTING_TYPE::LEVEL_ADJUSTMENT, - ASM_SETTING_TYPE::LEVEL_ADJUSTMENT, - asmId, - asmLevel - )); - - std::lock_guard guard(this->_propertyMtx); - this->_ambientSoundControl.fulfill(); - this->_asmLevel.fulfill(); - this->_focusOnVoice.fulfill(); - } - - if (!(this->_vptType.isFulfilled() && this->_surroundPosition.isFulfilled())) { - VPT_INQUIRED_TYPE command; - unsigned char preset; - - if (this->_vptType.desired != 0) { - command = VPT_INQUIRED_TYPE::VPT; - preset = static_cast(this->_vptType.desired); - } - else if (this->_surroundPosition.desired != SOUND_POSITION_PRESET::OFF) { - command = VPT_INQUIRED_TYPE::SOUND_POSITION; - preset = static_cast(this->_surroundPosition.desired); - } - else { - // Just used that one because it seems like it disables both - if (this->_surroundPosition.current != SOUND_POSITION_PRESET::OFF) { - command = VPT_INQUIRED_TYPE::SOUND_POSITION; - preset = static_cast(SOUND_POSITION_PRESET::OFF); - } - else if (this->_vptType.current != 0) { - command = VPT_INQUIRED_TYPE::VPT; - preset = 0; - } - else { - throw std::logic_error("it's impossible that both values were changed to zero and were also previously zero"); - } - } - - this->_conn.sendCommand(CommandSerializer::serializeVPTSetting(command, preset)); - - std::lock_guard guard(this->_propertyMtx); - this->_vptType.fulfill(); - this->_surroundPosition.fulfill(); - } - - - // THIS IS A WORKAROUND - // My XM4 do not respond when 2 commands of the same function are sent back to back - // This command breaks the chain and makes it respond every time - { - this->_conn.sendCommand({0x36, 0x01}, DATA_TYPE::DATA_MDR_NO2); - } -} - -void Headphones::setStateFromReply(BtMessage replyMessage) -{ - Buffer bytes = replyMessage.messageBytes; - COMMAND_TYPE cmd = static_cast(bytes[0]); - - switch (cmd) - { - case COMMAND_TYPE::DEVICES_QUERY_RESPONSE: - { - if (bytes[1] != 1) - // Wrong query type, break - break; - - int idx = 3; - int numDevices = static_cast(bytes[2]); - for (; numDevices > 0; numDevices--) - { - std::string mac_addr = ""; - for (int i = idx; i(bytes[idx]); - - idx++; - int name_length = static_cast(bytes[idx]); - idx++; - std::string name = ""; - for (int i = idx; i_savedDevices.push_back(dev); - if (number == 1) - this->_dev1 = this->_savedDevices[this->_savedDevices.size()-1]; - if (number == 2) - this->_dev2 = this->_savedDevices[this->_savedDevices.size()-1]; - } - std::cout<<"Connected devices received:\n"; - for (auto &dev: this->_savedDevices) - { - std::cout<< dev.name<<": "< + +Headphones::Headphones(BluetoothWrapper& conn) : +_conn(conn) +{ + std::vector devices({BluetoothDevice({"",""})}); + this->_savedDevices = devices; +} + +void Headphones::setAmbientSoundControl(bool val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_ambientSoundControl.desired = val; +} + +bool Headphones::getAmbientSoundControl() +{ + return this->_ambientSoundControl.current; +} + +bool Headphones::isFocusOnVoiceAvailable() +{ + return this->_ambientSoundControl.current && this->_asmLevel.current > MINIMUM_VOICE_FOCUS_STEP; +} + +void Headphones::setFocusOnVoice(bool val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_focusOnVoice.desired = val; +} + +bool Headphones::getFocusOnVoice() +{ + return this->_focusOnVoice.current; +} + +bool Headphones::isSetAsmLevelAvailable() +{ + return this->_ambientSoundControl.current; +} + +void Headphones::setAsmLevel(int val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_asmLevel.desired = val; +} + +int Headphones::getAsmLevel() +{ + return this->_asmLevel.current; +} + +void Headphones::setOptimizerState(OPTIMIZER_STATE val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_optimizerState.desired = val; +} + +OPTIMIZER_STATE Headphones::getOptimizerState() +{ + return this->_optimizerState.current; +} + +void Headphones::setS2CToggle(S2C_TOGGLE val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_speakToChat.desired = val; +} + +void Headphones::setS2COptions(int sensitivity, bool voice, int offTime) +{ + unsigned int sens = (unsigned int) sensitivity; + unsigned int time = (unsigned int) offTime; + std::lock_guard guard(this->_propertyMtx); + this->_s2cOptions.desired = { (sens << 16) | (voice << 8) | (time) }; +} + +S2C_TOGGLE Headphones::getS2CToggle() +{ + return this->_speakToChat.current; +} + +unsigned int Headphones::getS2COptions() +{ + return this->_s2cOptions.current; +} + +void Headphones::setSurroundPosition(SOUND_POSITION_PRESET val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_surroundPosition.desired = val; +} + +SOUND_POSITION_PRESET Headphones::getSurroundPosition() +{ + return this->_surroundPosition.current; +} + +void Headphones::setVptType(int val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_vptType.desired = val; +} + +int Headphones::getVptType() +{ + return this->_vptType.current; +} + +bool Headphones::getMultiPointSetting() +{ + return this->_multiPointSetting; +} + +const std::vector& Headphones::getDevices() +{ + return this->_savedDevices; +} + +std::pair Headphones::getConnectedDevices() +{ + return {this->_dev1.current, this->_dev2.current}; +} + +void Headphones::setMultiPointConnection(int connectionId, int newDevice, int oldDevice) +{ + Property& dev = this->_dev1; + + if (connectionId==1) + dev = this->_dev1; + else + dev = this->_dev2; + + std::lock_guard guard(this->_propertyMtx); + dev.desired = newDevice; +} + +inline void Headphones::disconnect(int deviceIdx) +{ + std::string deviceMac = this->_savedDevices[deviceIdx].mac; + this->_conn.sendCommand(CommandSerializer::serializeMultiPointCommand( + MULTI_POINT_COMMANDS::DISCONNECT, + deviceMac + ), + DATA_TYPE::DATA_MDR_NO2 + ); +} + +inline void Headphones::connect(int deviceIdx) +{ + std::string deviceMac = this->_savedDevices[deviceIdx].mac; + this->_conn.sendCommand(CommandSerializer::serializeMultiPointCommand( + MULTI_POINT_COMMANDS::CONNECT, + deviceMac + ), + DATA_TYPE::DATA_MDR_NO2 + ); +} + +bool Headphones::isChanged() +{ + return !(this->_ambientSoundControl.isFulfilled() && + this->_asmLevel.isFulfilled() && + this->_focusOnVoice.isFulfilled() && + this->_surroundPosition.isFulfilled() && + this->_vptType.isFulfilled() && + this->_optimizerState.isFulfilled() && + this->_speakToChat.isFulfilled() && + this->_s2cOptions.isFulfilled() && + this->_dev1.isFulfilled() && + this->_dev2.isFulfilled() + ); +} + +// At most one instance of this function is invoked at any time +// Synchronization not required +void Headphones::setChanges() +{ + if (!(this->_optimizerState.isFulfilled())) + { + auto state = this->_optimizerState.desired; + + this->_conn.sendCommand(CommandSerializer::serializeXM4OptimizeCommand( + state + )); + + std::lock_guard guard(this->_propertyMtx); + this->_optimizerState.fulfill(); + } + + if (!(this->_speakToChat.isFulfilled())) + { + auto s2cState = this->_speakToChat.desired; + + this->_conn.sendCommand(CommandSerializer::serializeXM4SpeakToChat( + s2cState + )); + + std::lock_guard guard(this->_propertyMtx); + this->_speakToChat.fulfill(); + } + + if (!(this->_s2cOptions.isFulfilled())) + { + auto s2cOptions = this->_s2cOptions.desired; + unsigned char sensitivity = (unsigned char) (s2cOptions >> 16) & 0xff; + unsigned char voice = (unsigned char) (s2cOptions >> 8) & 0xff; + unsigned char offTime = (unsigned char) (s2cOptions) & 0xff; + + this->_conn.sendCommand(CommandSerializer::serializeXM4_S2C_Options( + sensitivity, + voice, + offTime + )); + + std::lock_guard guard(this->_propertyMtx); + this->_s2cOptions.fulfill(); + } + + if (!(this->_ambientSoundControl.isFulfilled() && this->_focusOnVoice.isFulfilled() && this->_asmLevel.isFulfilled())) + { + auto ncAsmEffect = this->_ambientSoundControl.desired ? NC_ASM_EFFECT::ADJUSTMENT_COMPLETION : NC_ASM_EFFECT::OFF; + auto asmId = this->_focusOnVoice.desired ? ASM_ID::VOICE : ASM_ID::NORMAL; + auto asmLevel = this->_ambientSoundControl.desired ? this->_asmLevel.desired : ASM_LEVEL_DISABLED; + + this->_conn.sendCommand(CommandSerializer::serializeNcAndAsmSetting( + ncAsmEffect, + NC_ASM_SETTING_TYPE::LEVEL_ADJUSTMENT, + ASM_SETTING_TYPE::LEVEL_ADJUSTMENT, + asmId, + asmLevel + )); + + std::lock_guard guard(this->_propertyMtx); + this->_ambientSoundControl.fulfill(); + this->_asmLevel.fulfill(); + this->_focusOnVoice.fulfill(); + } + + if (!(this->_vptType.isFulfilled() && this->_surroundPosition.isFulfilled())) { + VPT_INQUIRED_TYPE command; + unsigned char preset; + + if (this->_vptType.desired != 0) { + command = VPT_INQUIRED_TYPE::VPT; + preset = static_cast(this->_vptType.desired); + } + else if (this->_surroundPosition.desired != SOUND_POSITION_PRESET::OFF) { + command = VPT_INQUIRED_TYPE::SOUND_POSITION; + preset = static_cast(this->_surroundPosition.desired); + } + else { + // Just used that one because it seems like it disables both + if (this->_surroundPosition.current != SOUND_POSITION_PRESET::OFF) { + command = VPT_INQUIRED_TYPE::SOUND_POSITION; + preset = static_cast(SOUND_POSITION_PRESET::OFF); + } + else if (this->_vptType.current != 0) { + command = VPT_INQUIRED_TYPE::VPT; + preset = 0; + } + else { + throw std::logic_error("it's impossible that both values were changed to zero and were also previously zero"); + } + } + + this->_conn.sendCommand(CommandSerializer::serializeVPTSetting(command, preset)); + + std::lock_guard guard(this->_propertyMtx); + this->_vptType.fulfill(); + this->_surroundPosition.fulfill(); + } + + if (!(this->_dev1.isFulfilled())) + { + if (this->_dev1.current!=0) + this->disconnect(this->_dev1.current); + if (this->_dev1.desired!=0) + this->connect(this->_dev1.desired); + + std::lock_guard guard(this->_propertyMtx); + this->_dev1.fulfill(); + } + + if (!(this->_dev2.isFulfilled())) + { + if (this->_dev2.current!=0) + this->disconnect(this->_dev2.current); + if (this->_dev2.desired!=0) + this->connect(this->_dev2.desired); + + std::lock_guard guard(this->_propertyMtx); + this->_dev2.fulfill(); + } + + // THIS IS A WORKAROUND + // My XM4 do not respond when 2 commands of the same function are sent back to back + // This command breaks the chain and makes it respond every time + // And I can't seem to be able to fix it + { + // this->_conn.sendCommand({0x36, 0x01}, DATA_TYPE::DATA_MDR_NO2); + this->_conn.sendCommand({0x30, 0x01}, DATA_TYPE::DATA_MDR_NO2); + } +} + +void Headphones::setStateFromReply(BtMessage replyMessage) +{ + Buffer bytes = replyMessage.messageBytes; + COMMAND_TYPE cmd = static_cast(bytes[0]); + + switch (cmd) + { + case COMMAND_TYPE::DEVICES_QUERY_RESPONSE: + case COMMAND_TYPE::DEVICES_STATE_RESPONSE: + { + if (bytes[1] != 0x01) + // Wrong query type, break + break; + + std::vector savedDevices = std::vector({BluetoothDevice("","")}); + int dev1 = 0, dev2 = 0; + int idx = 3; + int numDevices = static_cast(bytes[2]); + for (; numDevices > 0; numDevices--) + { + std::string mac_addr = ""; + for (int i = idx; i(bytes[idx]); + + idx++; + unsigned char name_length = static_cast(bytes[idx]); + idx++; + std::string name = ""; + for (int i = idx; i_savedDevices.end() - this->_savedDevices.begin() - 1); + dev1 = savedDevices.size()-1; + std::cout<< "device 1: "<< dev1 << std::endl; + } + if (number == 0x02) + { + // dev2 = (this->_savedDevices.end() - this->_savedDevices.begin() - 1); + dev2 = savedDevices.size()-1; + std::cout<< "device 2: "<< dev2 << std::endl; + } + } + std::cout<<"Connected devices received:\n"; + for (auto &dev: savedDevices) + { + std::cout<< dev.name<<": "<_propertyMtx); + this->_savedDevices = std::move(savedDevices); + this->_dev1.setState(dev1); + this->_dev2.setState(dev2); + } + break; + } + + case COMMAND_TYPE::XM4_OPTIMIZER_RESPONSE: + { + if (bytes[2] != 0x00) + this->_optimizerState.setState(OPTIMIZER_STATE::OPTIMIZING); + else + this->_optimizerState.setState(OPTIMIZER_STATE::IDLE); + break; + } + + default: + break; + } +} diff --git a/Client/Headphones.h b/Client/Headphones.h index 1dc775a..1fc1498 100644 --- a/Client/Headphones.h +++ b/Client/Headphones.h @@ -1,90 +1,101 @@ -#pragma once - -#include "SingleInstanceFuture.h" -#include "BluetoothWrapper.h" -#include "Constants.h" - -#include -#include - -template -struct Property { - T current; - T desired; - - void fulfill(); - bool isFulfilled(); - void setState(T val); -}; - -class Headphones { -public: - Headphones(BluetoothWrapper& conn); - - void setAmbientSoundControl(bool val); - bool getAmbientSoundControl(); - - bool isFocusOnVoiceAvailable(); - void setFocusOnVoice(bool val); - bool getFocusOnVoice(); - - bool isSetAsmLevelAvailable(); - void setAsmLevel(int val); - int getAsmLevel(); - - void setOptimizerState(OPTIMIZER_STATE val); - OPTIMIZER_STATE getOptimizerState(); - - void setS2CToggle(S2C_TOGGLE val); - void setS2COptions(int sensitivity, bool voice, int offTime); - S2C_TOGGLE getS2CToggle(); - unsigned int getS2COptions(); - - void setSurroundPosition(SOUND_POSITION_PRESET val); - SOUND_POSITION_PRESET getSurroundPosition(); - - void setVptType(int val); - int getVptType(); - - bool isChanged(); - void setChanges(); - - void setStateFromReply(BtMessage replyMessage); -private: - Property _ambientSoundControl = { 0 }; - Property _focusOnVoice = { 0 }; - Property _asmLevel = { 0 }; - - Property _surroundPosition = { SOUND_POSITION_PRESET::OUT_OF_RANGE, SOUND_POSITION_PRESET::OFF }; - Property _vptType = { 0 }; - - Property _optimizerState = { OPTIMIZER_STATE::IDLE }; - - Property _speakToChat = { S2C_TOGGLE::INACTIVE, S2C_TOGGLE::INACTIVE }; - Property _s2cOptions = { 0 }; - - std::vector _savedDevices; - BluetoothDevice _dev1, _dev2; - std::mutex _propertyMtx; - std::mutex _sendMtx; - - BluetoothWrapper& _conn; -}; - -template -inline void Property::fulfill() -{ - this->current = this->desired; -} - -template -inline bool Property::isFulfilled() -{ - return this->desired == this->current; -} - -template -inline void Property::setState(T val) -{ - this->current = val; +#pragma once + +#include "SingleInstanceFuture.h" +#include "BluetoothWrapper.h" +#include "Constants.h" + +#include +#include +#include +#include + +template +struct Property { + T current; + T desired; + + void fulfill(); + bool isFulfilled(); + void setState(T val); +}; + +class Headphones { +public: + Headphones(BluetoothWrapper& conn); + + void setAmbientSoundControl(bool val); + bool getAmbientSoundControl(); + + bool isFocusOnVoiceAvailable(); + void setFocusOnVoice(bool val); + bool getFocusOnVoice(); + + bool isSetAsmLevelAvailable(); + void setAsmLevel(int val); + int getAsmLevel(); + + void setOptimizerState(OPTIMIZER_STATE val); + OPTIMIZER_STATE getOptimizerState(); + + void setS2CToggle(S2C_TOGGLE val); + void setS2COptions(int sensitivity, bool voice, int offTime); + S2C_TOGGLE getS2CToggle(); + unsigned int getS2COptions(); + + void setSurroundPosition(SOUND_POSITION_PRESET val); + SOUND_POSITION_PRESET getSurroundPosition(); + + void setVptType(int val); + int getVptType(); + + bool getMultiPointSetting(); + const std::vector& getDevices(); + std::pair getConnectedDevices(); + void setMultiPointConnection(int connectionId, int newDevice, int oldDevice); + inline void disconnect(int deviceIdx); + inline void connect(int deviceIdx); + + bool isChanged(); + void setChanges(); + + void setStateFromReply(BtMessage replyMessage); +private: + Property _ambientSoundControl = { 0 }; + Property _focusOnVoice = { 0 }; + Property _asmLevel = { 0 }; + + Property _surroundPosition = { SOUND_POSITION_PRESET::OUT_OF_RANGE, SOUND_POSITION_PRESET::OFF }; + Property _vptType = { 0 }; + + Property _optimizerState = { OPTIMIZER_STATE::IDLE }; + + Property _speakToChat = { S2C_TOGGLE::INACTIVE, S2C_TOGGLE::INACTIVE }; + Property _s2cOptions = { 0 }; + + std::vector _savedDevices; + Property _dev1 = { 0, 0 }, _dev2 = { 0, 0 }; + bool _multiPointSetting = true; + + std::mutex _propertyMtx; + + BluetoothWrapper& _conn; +}; + +template +inline void Property::fulfill() +{ + this->current = this->desired; +} + +template +inline bool Property::isFulfilled() +{ + return this->desired == this->current; +} + +template +inline void Property::setState(T val) +{ + this->current = val; + this->desired = val; } \ No newline at end of file diff --git a/Client/Listener.cpp b/Client/Listener.cpp index 9ba90d1..c37c2a4 100644 --- a/Client/Listener.cpp +++ b/Client/Listener.cpp @@ -1,39 +1,40 @@ -#include "Listener.h" - -Listener::Listener(Headphones& headphones, BluetoothWrapper& bt): -_headphones(headphones), -_bt(bt) -{} - -void Listener::listen() -{ - std::cout << "Listener registered." << std::endl; - for(;;) - { - Buffer reply = this->_bt.readReplies(); - this->handle_message(reply); - } -} - -inline BtMessage Listener::parse(Buffer msg) -{ - return CommandSerializer::unpackBtMessage(msg); -} - -void Listener::handle_message(Buffer msg) -{ - BtMessage m = this->parse(msg); - this->_bt.setSeqNumber(m.seqNumber); - - if (m.dataType == DATA_TYPE::ACK) - { - this->_bt.postAck(); - this->_bt._ack.notify_one(); - } - else if (m.dataType == DATA_TYPE::DATA_MDR || m.dataType == DATA_TYPE::DATA_MDR_NO2) - { - // Set these values as current values of Headphone property - this->_headphones.setStateFromReply(m); - this->_bt.sendCommand({}, DATA_TYPE::ACK); - } -} +#include "Listener.h" + +Listener::Listener(Headphones& headphones, BluetoothWrapper& bt): +_headphones(headphones), +_bt(bt) +{} + +void Listener::listen() +{ + std::cout << "Listener registered." << std::endl; + for(;;) + { + Buffer reply = this->_bt.readReplies(); + this->handle_message(reply); + } +} + +inline BtMessage Listener::parse(Buffer msg) +{ + return CommandSerializer::unpackBtMessage(msg); +} + +void Listener::handle_message(Buffer msg) +{ + BtMessage m = this->parse(msg); + this->_bt.setSeqNumber(m.seqNumber); + + if (m.dataType == DATA_TYPE::ACK) + { + std::cout<< "Received Ack"<_bt.postAck(); + this->_bt._ack.notify_all(); + } + else if (m.dataType == DATA_TYPE::DATA_MDR || m.dataType == DATA_TYPE::DATA_MDR_NO2) + { + // Set these values as current values of Headphone property + this->_headphones.setStateFromReply(m); + this->_bt.sendCommand({}, DATA_TYPE::ACK); + } +} From 9ef2969c013cf29032c74636357ccfa2b3539be7 Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Fri, 8 Sep 2023 22:30:53 +0530 Subject: [PATCH 10/15] multi-point and device state query added --- Client/BluetoothWrapper.cpp | 18 +++++++--- Client/CMakeLists.txt | 2 +- Client/Constants.h | 7 +++- Client/CrossPlatformGUI.cpp | 7 ++-- Client/Headphones.cpp | 70 ++++++++++++++++++++++++++++--------- Client/Headphones.h | 6 ++++ Client/Listener.cpp | 1 - 7 files changed, 82 insertions(+), 29 deletions(-) diff --git a/Client/BluetoothWrapper.cpp b/Client/BluetoothWrapper.cpp index 5fc1acc..01265fb 100644 --- a/Client/BluetoothWrapper.cpp +++ b/Client/BluetoothWrapper.cpp @@ -86,14 +86,12 @@ void BluetoothWrapper::_waitForAck() this->_ack.wait(guard); } - std::cout<<"Consuming..."<_ackBuffer--; } void BluetoothWrapper::postAck() { std::lock_guard guard(this->_dataMtx); - std::cout<<"Posting..."<_ackBuffer++; } @@ -106,11 +104,21 @@ Buffer BluetoothWrapper::readReplies() do { - auto numRecvd = this->_connector->recv(buf, sizeof(buf)); + int i = 0; + while(!messageFinished) + { + this->_connector->recv(buf+i, 1); + i++; + if (buf[i-1] == 0x3c) + messageFinished = true; + // break; + } + auto numRecvd = i; size_t messageStart = 0; size_t messageEnd = numRecvd; - // for(int i=0; i_drawSpeakToChat(); if (this->_headphones.getMultiPointSetting()) this->_drawMultiPointConn(); + ImGui::Separator(); this->_drawOptimizerButton(); } else { @@ -128,6 +129,8 @@ void CrossPlatformGUI::_drawDeviceDiscovery() // Add post-connection setup here this->_listener = std::make_unique(this->_headphones, this->_bt); auto useless_future = std::async(std::launch::async, &Listener::listen, this->_listener.get()); + if (this->_connectedDevice.name == "WH-1000XM4") + this->_headphones.queryState(); } ); } @@ -306,7 +309,6 @@ void CrossPlatformGUI::_drawMultiPointConn() ImGui::SameLine(); if (ImGui::Button("Disconnect device 1")) { - std::cout << "Button 1 pressed "<_headphones.setMultiPointConnection(1, 0, dev1); } } @@ -355,9 +357,6 @@ void CrossPlatformGUI::_drawMultiPointConn() ImGui::SameLine(); if (ImGui::Button("Disconnect device 2")) { - std::cout << "Button 2 pressed "<_headphones.setMultiPointConnection(2,0,dev2); - this->_headphones.setMultiPointConnection(2,0,dev2); this->_headphones.setMultiPointConnection(2,0,dev2); } } diff --git a/Client/Headphones.cpp b/Client/Headphones.cpp index e045007..1de32ec 100644 --- a/Client/Headphones.cpp +++ b/Client/Headphones.cpp @@ -127,15 +127,15 @@ std::pair Headphones::getConnectedDevices() void Headphones::setMultiPointConnection(int connectionId, int newDevice, int oldDevice) { - Property& dev = this->_dev1; + Property* dev = & this->_dev1; if (connectionId==1) - dev = this->_dev1; + dev = & this->_dev1; else - dev = this->_dev2; + dev = & this->_dev2; std::lock_guard guard(this->_propertyMtx); - dev.desired = newDevice; + dev->desired = newDevice; } inline void Headphones::disconnect(int deviceIdx) @@ -282,7 +282,7 @@ void Headphones::setChanges() this->connect(this->_dev1.desired); std::lock_guard guard(this->_propertyMtx); - this->_dev1.fulfill(); + this->_dev1.desired = this->_dev1.current; } if (!(this->_dev2.isFulfilled())) @@ -293,7 +293,7 @@ void Headphones::setChanges() this->connect(this->_dev2.desired); std::lock_guard guard(this->_propertyMtx); - this->_dev2.fulfill(); + this->_dev2.desired = this->_dev2.current; } // THIS IS A WORKAROUND @@ -301,11 +301,18 @@ void Headphones::setChanges() // This command breaks the chain and makes it respond every time // And I can't seem to be able to fix it { - // this->_conn.sendCommand({0x36, 0x01}, DATA_TYPE::DATA_MDR_NO2); - this->_conn.sendCommand({0x30, 0x01}, DATA_TYPE::DATA_MDR_NO2); + this->_conn.sendCommand({0x02, 0x01}, DATA_TYPE::DATA_MDR_NO2); } } +void Headphones::queryState() +{ + this->queryMultiPointSetting(); + this->queryDevices(); + // this->queryS2C(); + // this->queryS2COptions(); +} + void Headphones::setStateFromReply(BtMessage replyMessage) { Buffer bytes = replyMessage.messageBytes; @@ -351,22 +358,13 @@ void Headphones::setStateFromReply(BtMessage replyMessage) savedDevices.push_back(dev); if (number == 0x01) { - // dev1 = (this->_savedDevices.end() - this->_savedDevices.begin() - 1); dev1 = savedDevices.size()-1; - std::cout<< "device 1: "<< dev1 << std::endl; } if (number == 0x02) { - // dev2 = (this->_savedDevices.end() - this->_savedDevices.begin() - 1); dev2 = savedDevices.size()-1; - std::cout<< "device 2: "<< dev2 << std::endl; } } - std::cout<<"Connected devices received:\n"; - for (auto &dev: savedDevices) - { - std::cout<< dev.name<<": "<_propertyMtx); @@ -386,7 +384,45 @@ void Headphones::setStateFromReply(BtMessage replyMessage) break; } + case COMMAND_TYPE::MULTI_POINT_SETTING_RESPONSE: + { + if (bytes[4] == 0x38) + this->_multiPointSetting = true; + else + this->_multiPointSetting = false; + + break; + } + default: break; } } + +void Headphones::queryMultiPointSetting() +{ + this->_conn.sendCommand({ + static_cast(COMMAND_TYPE::MULTI_POINT_SETTING_QUERY), + 0x00 + }); +} + +void Headphones::queryDevices() +{ + this->_conn.sendCommand({ + static_cast(COMMAND_TYPE::MULTI_POINT_DEVICES_QUERY), + 0x01 + }, + DATA_TYPE::DATA_MDR_NO2 + ); +} + +void Headphones::queryS2C() +{ + +} + +void Headphones::queryS2COptions() +{ + +} \ No newline at end of file diff --git a/Client/Headphones.h b/Client/Headphones.h index 1fc1498..e238483 100644 --- a/Client/Headphones.h +++ b/Client/Headphones.h @@ -58,7 +58,13 @@ class Headphones { bool isChanged(); void setChanges(); + void queryState(); void setStateFromReply(BtMessage replyMessage); + + void queryMultiPointSetting(); + void queryDevices(); + void queryS2C(); + void queryS2COptions(); private: Property _ambientSoundControl = { 0 }; Property _focusOnVoice = { 0 }; diff --git a/Client/Listener.cpp b/Client/Listener.cpp index c37c2a4..8d054a0 100644 --- a/Client/Listener.cpp +++ b/Client/Listener.cpp @@ -27,7 +27,6 @@ void Listener::handle_message(Buffer msg) if (m.dataType == DATA_TYPE::ACK) { - std::cout<< "Received Ack"<_bt.postAck(); this->_bt._ack.notify_all(); } From 8ee7e468e1d9dbdb686d4b1d0daa737b0d61564f Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Tue, 12 Sep 2023 18:40:58 +0530 Subject: [PATCH 11/15] fixed issue where queries causing app to hang --- Client/BluetoothWrapper.cpp | 32 ++---- Client/BluetoothWrapper.h | 100 ++++++++--------- Client/CMakeLists.txt | 216 ++++++++++++++++++------------------ Client/Headphones.cpp | 13 ++- Client/Listener.cpp | 2 +- Client/Listener.h | 49 ++++---- 6 files changed, 198 insertions(+), 214 deletions(-) diff --git a/Client/BluetoothWrapper.cpp b/Client/BluetoothWrapper.cpp index 01265fb..7b16352 100644 --- a/Client/BluetoothWrapper.cpp +++ b/Client/BluetoothWrapper.cpp @@ -1,5 +1,4 @@ #include "BluetoothWrapper.h" -#include BluetoothWrapper::BluetoothWrapper(std::unique_ptr connector) { @@ -23,30 +22,12 @@ BluetoothWrapper& BluetoothWrapper::operator=(BluetoothWrapper&& other) noexcept return *this; } -int BluetoothWrapper::sendCommand(const std::vector& bytes) -{ - int bytesSent; - { - std::lock_guard guard(this->_connectorMtx); - std::lock_guard guard2(this->_dataMtx); - auto data = CommandSerializer::packageDataForBt(bytes, DATA_TYPE::DATA_MDR, this->_seqNumber ^ 0x01); - bytesSent = this->_connector->send(data.data(), data.size()); - } - - this->_waitForAck(); - - return bytesSent; -} - int BluetoothWrapper::sendCommand(const std::vector& bytes, DATA_TYPE dtype) { int bytesSent; - { - std::lock_guard guard(this->_connectorMtx); - std::lock_guard guard2(this->_dataMtx); - auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber ^ 0x01); - bytesSent = this->_connector->send(data.data(), data.size()); - } + std::lock_guard guard(this->_connectorMtx); + auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber ^ 0x01); + bytesSent = this->_connector->send(data.data(), data.size()); if (dtype != DATA_TYPE::ACK) this->_waitForAck(); @@ -54,6 +35,12 @@ int BluetoothWrapper::sendCommand(const std::vector& bytes, DATA_TYPE dtyp return bytesSent; } +void BluetoothWrapper::sendAck() +{ + auto data = CommandSerializer::packageDataForBt({}, DATA_TYPE::ACK, this->_seqNumber ^ 0x01); + this->_connector->send(data.data(), data.size()); +} + bool BluetoothWrapper::isConnected() noexcept { return this->_connector->isConnected(); @@ -72,7 +59,6 @@ void BluetoothWrapper::disconnect() noexcept this->_connector->disconnect(); } - std::vector BluetoothWrapper::getConnectedDevices() { return this->_connector->getConnectedDevices(); diff --git a/Client/BluetoothWrapper.h b/Client/BluetoothWrapper.h index 7f8bc46..f6452a1 100644 --- a/Client/BluetoothWrapper.h +++ b/Client/BluetoothWrapper.h @@ -1,50 +1,50 @@ -#pragma once - -#include "IBluetoothConnector.h" -#include "CommandSerializer.h" -#include "Constants.h" -#include -#include -#include -#include -#include - - -//Thread-safety: This class is thread-safe. -class BluetoothWrapper -{ -public: - BluetoothWrapper(std::unique_ptr connector); - - BluetoothWrapper(const BluetoothWrapper&) = delete; - BluetoothWrapper& operator=(const BluetoothWrapper&) = delete; - - BluetoothWrapper(BluetoothWrapper&& other) noexcept; - BluetoothWrapper& operator=(BluetoothWrapper&& other) noexcept; - - int sendCommand(const std::vector& bytes); - int sendCommand(const std::vector& bytes, DATA_TYPE dtype); - - Buffer readReplies(); - - bool isConnected() noexcept; - //Try to connect to the headphones - void connect(const std::string& addr); - void disconnect() noexcept; - - std::vector getConnectedDevices(); - void setSeqNumber(unsigned char seqNumber); - void postAck(); - -private: - void _waitForAck(); - - std::unique_ptr _connector; - std::mutex _connectorMtx; - std::mutex _dataMtx; - unsigned char _seqNumber = 0; - unsigned int _ackBuffer = 0; - -public: - std::condition_variable _ack; -}; +#pragma once + +#include "IBluetoothConnector.h" +#include "CommandSerializer.h" +#include "Constants.h" +#include +#include +#include +#include +#include +#include + +//Thread-safety: This class is thread-safe. +class BluetoothWrapper +{ +public: + BluetoothWrapper(std::unique_ptr connector); + + BluetoothWrapper(const BluetoothWrapper&) = delete; + BluetoothWrapper& operator=(const BluetoothWrapper&) = delete; + + BluetoothWrapper(BluetoothWrapper&& other) noexcept; + BluetoothWrapper& operator=(BluetoothWrapper&& other) noexcept; + + int sendCommand(const std::vector& bytes, DATA_TYPE dtype = DATA_TYPE::DATA_MDR); + void sendAck(); + + Buffer readReplies(); + + bool isConnected() noexcept; + //Try to connect to the headphones + void connect(const std::string& addr); + void disconnect() noexcept; + + std::vector getConnectedDevices(); + void setSeqNumber(unsigned char seqNumber); + void postAck(); + +private: + void _waitForAck(); + + std::unique_ptr _connector; + std::mutex _connectorMtx; + std::mutex _dataMtx; + unsigned char _seqNumber = 0x01; + unsigned int _ackBuffer = 0; + +public: + std::condition_variable _ack; +}; diff --git a/Client/CMakeLists.txt b/Client/CMakeLists.txt index e0a8ac2..c7277a1 100644 --- a/Client/CMakeLists.txt +++ b/Client/CMakeLists.txt @@ -1,108 +1,108 @@ -cmake_minimum_required(VERSION 3.1 FATAL_ERROR) -project(SonyHeadphonesClient) - -set(CMAKE_CXX_STANDARD 20) - -find_package(Threads REQUIRED) - -add_executable(SonyHeadphonesClient) - -add_definitions(-D__HEADPHONES_APP_VERSION__="1.3.1") - -target_sources(SonyHeadphonesClient - PRIVATE BluetoothWrapper.cpp - ByteMagic.cpp - CommandSerializer.cpp - TimedMessageQueue.cpp - Listener.cpp - CrossPlatformGUI.cpp "Headphones.cpp") - -target_include_directories (SonyHeadphonesClient - PRIVATE ${CMAKE_SOURCE_DIR} -) - -if(UNIX AND NOT APPLE) - set(LINUX TRUE) -endif() - -if (WIN32 OR LINUX) - set (DEAR_IMGUI TRUE) -endif() - -if (DEAR_IMGUI) - target_sources(SonyHeadphonesClient - PRIVATE CrossPlatformGUI.cpp - imgui/imgui.cpp - imgui/imgui_draw.cpp - imgui/imgui_tables.cpp - imgui/imgui_widgets.cpp - CascadiaCodeFont.cpp - ) - - target_include_directories (SonyHeadphonesClient - PRIVATE ${CMAKE_SOURCE_DIR}/imgui - ) -endif () - -if (LINUX) - target_sources(SonyHeadphonesClient - PRIVATE linux/DBusHelper.cpp - linux/LinuxBluetoothConnector.cpp - linux/LinuxGUI.cpp - linux/main.cpp - imgui/backends/imgui_impl_glfw.cpp - imgui/backends/imgui_impl_opengl3.cpp - ) - - set (CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/linux) - - set (OpenGL_GL_PREFERENCE GLVND) - - find_package(DBus REQUIRED) - - find_package(GLEW REQUIRED) - - find_package(glfw3 REQUIRED) - - find_package(OpenGL REQUIRED) - - if ((NOT DBUS_FOUND) OR (NOT GLEW_FOUND) OR (NOT glfw3_FOUND) OR (NOT OPENGL_FOUND)) - message ( FATAL_ERROR - "Didn't find one of the packages. For Debian based systems, use:" - "sudo apt install libbluetooth-dev libglew-dev libglfw3-dev libdbus-1-dev" ) - endif () - - target_include_directories(SonyHeadphonesClient - PRIVATE ${DBUS_INCLUDE_DIRS} - ${OPENGL_INCLUDE_DIRS} - ) - - target_link_libraries(SonyHeadphonesClient - PRIVATE ${DBUS_LIBRARIES} - GLEW::GLEW - glfw - bluetooth - OpenGL::GL - ) - -endif () - -if (WIN32) - ADD_DEFINITIONS(-DUNICODE) - - target_sources(SonyHeadphonesClient - PRIVATE windows/WindowsGUI.cpp - windows/main.cpp - windows/WindowsBluetoothConnector.cpp - imgui/backends/imgui_impl_dx11.cpp - imgui/backends/imgui_impl_win32.cpp - ) - - target_link_libraries(SonyHeadphonesClient - PRIVATE d3d11 - ) -endif () - -target_link_libraries(SonyHeadphonesClient - PRIVATE Threads::Threads -) +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(SonyHeadphonesClient) + +set(CMAKE_CXX_STANDARD 20) + +find_package(Threads REQUIRED) + +add_executable(SonyHeadphonesClient) + +add_definitions(-D__HEADPHONES_APP_VERSION__="1.3.1") + +target_sources(SonyHeadphonesClient + PRIVATE BluetoothWrapper.cpp + ByteMagic.cpp + CommandSerializer.cpp + TimedMessageQueue.cpp + Listener.cpp + CrossPlatformGUI.cpp "Headphones.cpp") + +target_include_directories (SonyHeadphonesClient + PRIVATE ${CMAKE_SOURCE_DIR} +) + +if(UNIX AND NOT APPLE) + set(LINUX TRUE) +endif() + +if (WIN32 OR LINUX) + set (DEAR_IMGUI TRUE) +endif() + +if (DEAR_IMGUI) + target_sources(SonyHeadphonesClient + PRIVATE CrossPlatformGUI.cpp + imgui/imgui.cpp + imgui/imgui_draw.cpp + imgui/imgui_tables.cpp + imgui/imgui_widgets.cpp + CascadiaCodeFont.cpp + ) + + target_include_directories (SonyHeadphonesClient + PRIVATE ${CMAKE_SOURCE_DIR}/imgui + ) +endif () + +if (LINUX) + target_sources(SonyHeadphonesClient + PRIVATE linux/DBusHelper.cpp + linux/LinuxBluetoothConnector.cpp + linux/LinuxGUI.cpp + linux/main.cpp + imgui/backends/imgui_impl_glfw.cpp + imgui/backends/imgui_impl_opengl3.cpp + ) + + set (CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/linux) + + set (OpenGL_GL_PREFERENCE GLVND) + + find_package(DBus REQUIRED) + + find_package(GLEW REQUIRED) + + find_package(glfw3 REQUIRED) + + find_package(OpenGL REQUIRED) + + if ((NOT DBUS_FOUND) OR (NOT GLEW_FOUND) OR (NOT glfw3_FOUND) OR (NOT OPENGL_FOUND)) + message ( FATAL_ERROR + "Didn't find one of the packages. For Debian based systems, use:" + "sudo apt install libbluetooth-dev libglew-dev libglfw3-dev libdbus-1-dev" ) + endif () + + target_include_directories(SonyHeadphonesClient + PRIVATE ${DBUS_INCLUDE_DIRS} + ${OPENGL_INCLUDE_DIRS} + ) + + target_link_libraries(SonyHeadphonesClient + PRIVATE ${DBUS_LIBRARIES} + GLEW::GLEW + glfw + bluetooth + OpenGL::GL + ) + +endif () + +if (WIN32) + ADD_DEFINITIONS(-DUNICODE) + + target_sources(SonyHeadphonesClient + PRIVATE windows/WindowsGUI.cpp + windows/main.cpp + windows/WindowsBluetoothConnector.cpp + imgui/backends/imgui_impl_dx11.cpp + imgui/backends/imgui_impl_win32.cpp + ) + + target_link_libraries(SonyHeadphonesClient + PRIVATE d3d11 + ) +endif () + +target_link_libraries(SonyHeadphonesClient + PRIVATE Threads::Threads +) diff --git a/Client/Headphones.cpp b/Client/Headphones.cpp index 1de32ec..c770a84 100644 --- a/Client/Headphones.cpp +++ b/Client/Headphones.cpp @@ -300,17 +300,20 @@ void Headphones::setChanges() // My XM4 do not respond when 2 commands of the same function are sent back to back // This command breaks the chain and makes it respond every time // And I can't seem to be able to fix it + // This also queries paired devices { - this->_conn.sendCommand({0x02, 0x01}, DATA_TYPE::DATA_MDR_NO2); + this->_conn.sendCommand({0x36,0x01}, DATA_TYPE::DATA_MDR_NO2); } } void Headphones::queryState() { + // Right now only one query is placed in here + // When adding more queries only the first query is ACK'd + // This is because query commands do not follow the same rule as other commands regarding sequence number + // The logic is quite weird + // TODO: fix this this->queryMultiPointSetting(); - this->queryDevices(); - // this->queryS2C(); - // this->queryS2COptions(); } void Headphones::setStateFromReply(BtMessage replyMessage) @@ -403,7 +406,7 @@ void Headphones::queryMultiPointSetting() { this->_conn.sendCommand({ static_cast(COMMAND_TYPE::MULTI_POINT_SETTING_QUERY), - 0x00 + 0x01 }); } diff --git a/Client/Listener.cpp b/Client/Listener.cpp index 8d054a0..564759e 100644 --- a/Client/Listener.cpp +++ b/Client/Listener.cpp @@ -34,6 +34,6 @@ void Listener::handle_message(Buffer msg) { // Set these values as current values of Headphone property this->_headphones.setStateFromReply(m); - this->_bt.sendCommand({}, DATA_TYPE::ACK); + this->_bt.sendAck(); } } diff --git a/Client/Listener.h b/Client/Listener.h index 4603b84..7bedf98 100644 --- a/Client/Listener.h +++ b/Client/Listener.h @@ -1,28 +1,23 @@ -#pragma once - -#include "Constants.h" -#include "BluetoothWrapper.h" -#include "CommandSerializer.h" -#include "Exceptions.h" -#include "Headphones.h" - -#include -#include - -class Listener -{ - /* - Listen for and read incoming messages - Parse messages - Call message handler - */ -public: - Listener(Headphones& headphones, BluetoothWrapper& bt); - void listen(); - inline BtMessage parse(Buffer msg); - void handle_message(Buffer msg); - -private: - Headphones& _headphones; - BluetoothWrapper& _bt; +#pragma once + +#include "Constants.h" +#include "BluetoothWrapper.h" +#include "CommandSerializer.h" +#include "Exceptions.h" +#include "Headphones.h" + +#include +#include + +class Listener +{ +public: + Listener(Headphones& headphones, BluetoothWrapper& bt); + void listen(); + inline BtMessage parse(Buffer msg); + void handle_message(Buffer msg); + +private: + Headphones& _headphones; + BluetoothWrapper& _bt; }; \ No newline at end of file From 2f6245ab755f17ae0f5504b3de5ca0c813562485 Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Tue, 12 Sep 2023 18:51:35 +0530 Subject: [PATCH 12/15] converted CRLF to LF --- Client/BluetoothWrapper.cpp | 286 ++++++------ Client/BluetoothWrapper.h | 100 ++-- Client/CMakeLists.txt | 216 ++++----- Client/CommandSerializer.cpp | 508 ++++++++++----------- Client/CommandSerializer.h | 100 ++-- Client/Constants.h | 334 +++++++------- Client/CrossPlatformGUI.cpp | 850 +++++++++++++++++----------------- Client/CrossPlatformGUI.h | 114 ++--- Client/Headphones.cpp | 860 +++++++++++++++++------------------ Client/Headphones.h | 212 ++++----- Client/Listener.cpp | 78 ++-- Client/Listener.h | 44 +- 12 files changed, 1851 insertions(+), 1851 deletions(-) diff --git a/Client/BluetoothWrapper.cpp b/Client/BluetoothWrapper.cpp index 7b16352..21157f7 100644 --- a/Client/BluetoothWrapper.cpp +++ b/Client/BluetoothWrapper.cpp @@ -1,143 +1,143 @@ -#include "BluetoothWrapper.h" - -BluetoothWrapper::BluetoothWrapper(std::unique_ptr connector) -{ - this->_connector.swap(connector); -} - -BluetoothWrapper::BluetoothWrapper(BluetoothWrapper&& other) noexcept -{ - this->_connector.swap(other._connector); - this->_seqNumber = other._seqNumber; -} - -BluetoothWrapper& BluetoothWrapper::operator=(BluetoothWrapper&& other) noexcept -{ - //self assignment - if (this == &other) return *this; - - this->_connector.swap(other._connector); - this->_seqNumber = other._seqNumber; - - return *this; -} - -int BluetoothWrapper::sendCommand(const std::vector& bytes, DATA_TYPE dtype) -{ - int bytesSent; - std::lock_guard guard(this->_connectorMtx); - auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber ^ 0x01); - bytesSent = this->_connector->send(data.data(), data.size()); - - if (dtype != DATA_TYPE::ACK) - this->_waitForAck(); - - return bytesSent; -} - -void BluetoothWrapper::sendAck() -{ - auto data = CommandSerializer::packageDataForBt({}, DATA_TYPE::ACK, this->_seqNumber ^ 0x01); - this->_connector->send(data.data(), data.size()); -} - -bool BluetoothWrapper::isConnected() noexcept -{ - return this->_connector->isConnected(); -} - -void BluetoothWrapper::connect(const std::string& addr) -{ - std::lock_guard guard(this->_connectorMtx); - this->_connector->connect(addr); -} - -void BluetoothWrapper::disconnect() noexcept -{ - std::lock_guard guard(this->_connectorMtx); - this->_seqNumber = 0; - this->_connector->disconnect(); -} - -std::vector BluetoothWrapper::getConnectedDevices() -{ - return this->_connector->getConnectedDevices(); -} - -void BluetoothWrapper::_waitForAck() -{ - std::unique_lock 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; - char buf[MAX_BLUETOOTH_MESSAGE_SIZE] = { 0 }; - Buffer msgBytes; - - do - { - int i = 0; - while(!messageFinished) - { - this->_connector->recv(buf+i, 1); - i++; - if (buf[i-1] == 0x3c) - messageFinished = true; - // 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; - - for (int i = 0; i < numRecvd; i++) - { - if (buf[i] == START_MARKER) - { - if (ongoingMessage) - { - throw RecoverableException("Invalid: Multiple start markers without an end marker", true); - } - messageStart = i + 1; - ongoingMessage = true; - } - else if (ongoingMessage && buf[i] == END_MARKER) - { - messageEnd = i; - ongoingMessage = false; - messageFinished = true; - } - } - - msgBytes.insert(msgBytes.end(), buf + messageStart, buf + messageEnd); - } while (!messageFinished); - - return msgBytes; - - auto msg = CommandSerializer::unpackBtMessage(msgBytes); - this->_seqNumber = msg.seqNumber; -} - -void BluetoothWrapper::setSeqNumber(unsigned char seqNumber) -{ - std::lock_guard guard(this->_dataMtx); - this->_seqNumber = seqNumber; -} +#include "BluetoothWrapper.h" + +BluetoothWrapper::BluetoothWrapper(std::unique_ptr connector) +{ + this->_connector.swap(connector); +} + +BluetoothWrapper::BluetoothWrapper(BluetoothWrapper&& other) noexcept +{ + this->_connector.swap(other._connector); + this->_seqNumber = other._seqNumber; +} + +BluetoothWrapper& BluetoothWrapper::operator=(BluetoothWrapper&& other) noexcept +{ + //self assignment + if (this == &other) return *this; + + this->_connector.swap(other._connector); + this->_seqNumber = other._seqNumber; + + return *this; +} + +int BluetoothWrapper::sendCommand(const std::vector& bytes, DATA_TYPE dtype) +{ + int bytesSent; + std::lock_guard guard(this->_connectorMtx); + auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber ^ 0x01); + bytesSent = this->_connector->send(data.data(), data.size()); + + if (dtype != DATA_TYPE::ACK) + this->_waitForAck(); + + return bytesSent; +} + +void BluetoothWrapper::sendAck() +{ + auto data = CommandSerializer::packageDataForBt({}, DATA_TYPE::ACK, this->_seqNumber ^ 0x01); + this->_connector->send(data.data(), data.size()); +} + +bool BluetoothWrapper::isConnected() noexcept +{ + return this->_connector->isConnected(); +} + +void BluetoothWrapper::connect(const std::string& addr) +{ + std::lock_guard guard(this->_connectorMtx); + this->_connector->connect(addr); +} + +void BluetoothWrapper::disconnect() noexcept +{ + std::lock_guard guard(this->_connectorMtx); + this->_seqNumber = 0; + this->_connector->disconnect(); +} + +std::vector BluetoothWrapper::getConnectedDevices() +{ + return this->_connector->getConnectedDevices(); +} + +void BluetoothWrapper::_waitForAck() +{ + std::unique_lock 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; + char buf[MAX_BLUETOOTH_MESSAGE_SIZE] = { 0 }; + Buffer msgBytes; + + do + { + int i = 0; + while(!messageFinished) + { + this->_connector->recv(buf+i, 1); + i++; + if (buf[i-1] == 0x3c) + messageFinished = true; + // 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; + + for (int i = 0; i < numRecvd; i++) + { + if (buf[i] == START_MARKER) + { + if (ongoingMessage) + { + throw RecoverableException("Invalid: Multiple start markers without an end marker", true); + } + messageStart = i + 1; + ongoingMessage = true; + } + else if (ongoingMessage && buf[i] == END_MARKER) + { + messageEnd = i; + ongoingMessage = false; + messageFinished = true; + } + } + + msgBytes.insert(msgBytes.end(), buf + messageStart, buf + messageEnd); + } while (!messageFinished); + + return msgBytes; + + auto msg = CommandSerializer::unpackBtMessage(msgBytes); + this->_seqNumber = msg.seqNumber; +} + +void BluetoothWrapper::setSeqNumber(unsigned char seqNumber) +{ + std::lock_guard guard(this->_dataMtx); + this->_seqNumber = seqNumber; +} diff --git a/Client/BluetoothWrapper.h b/Client/BluetoothWrapper.h index f6452a1..afa5dcd 100644 --- a/Client/BluetoothWrapper.h +++ b/Client/BluetoothWrapper.h @@ -1,50 +1,50 @@ -#pragma once - -#include "IBluetoothConnector.h" -#include "CommandSerializer.h" -#include "Constants.h" -#include -#include -#include -#include -#include -#include - -//Thread-safety: This class is thread-safe. -class BluetoothWrapper -{ -public: - BluetoothWrapper(std::unique_ptr connector); - - BluetoothWrapper(const BluetoothWrapper&) = delete; - BluetoothWrapper& operator=(const BluetoothWrapper&) = delete; - - BluetoothWrapper(BluetoothWrapper&& other) noexcept; - BluetoothWrapper& operator=(BluetoothWrapper&& other) noexcept; - - int sendCommand(const std::vector& bytes, DATA_TYPE dtype = DATA_TYPE::DATA_MDR); - void sendAck(); - - Buffer readReplies(); - - bool isConnected() noexcept; - //Try to connect to the headphones - void connect(const std::string& addr); - void disconnect() noexcept; - - std::vector getConnectedDevices(); - void setSeqNumber(unsigned char seqNumber); - void postAck(); - -private: - void _waitForAck(); - - std::unique_ptr _connector; - std::mutex _connectorMtx; - std::mutex _dataMtx; - unsigned char _seqNumber = 0x01; - unsigned int _ackBuffer = 0; - -public: - std::condition_variable _ack; -}; +#pragma once + +#include "IBluetoothConnector.h" +#include "CommandSerializer.h" +#include "Constants.h" +#include +#include +#include +#include +#include +#include + +//Thread-safety: This class is thread-safe. +class BluetoothWrapper +{ +public: + BluetoothWrapper(std::unique_ptr connector); + + BluetoothWrapper(const BluetoothWrapper&) = delete; + BluetoothWrapper& operator=(const BluetoothWrapper&) = delete; + + BluetoothWrapper(BluetoothWrapper&& other) noexcept; + BluetoothWrapper& operator=(BluetoothWrapper&& other) noexcept; + + int sendCommand(const std::vector& bytes, DATA_TYPE dtype = DATA_TYPE::DATA_MDR); + void sendAck(); + + Buffer readReplies(); + + bool isConnected() noexcept; + //Try to connect to the headphones + void connect(const std::string& addr); + void disconnect() noexcept; + + std::vector getConnectedDevices(); + void setSeqNumber(unsigned char seqNumber); + void postAck(); + +private: + void _waitForAck(); + + std::unique_ptr _connector; + std::mutex _connectorMtx; + std::mutex _dataMtx; + unsigned char _seqNumber = 0x01; + unsigned int _ackBuffer = 0; + +public: + std::condition_variable _ack; +}; diff --git a/Client/CMakeLists.txt b/Client/CMakeLists.txt index c7277a1..e0a8ac2 100644 --- a/Client/CMakeLists.txt +++ b/Client/CMakeLists.txt @@ -1,108 +1,108 @@ -cmake_minimum_required(VERSION 3.1 FATAL_ERROR) -project(SonyHeadphonesClient) - -set(CMAKE_CXX_STANDARD 20) - -find_package(Threads REQUIRED) - -add_executable(SonyHeadphonesClient) - -add_definitions(-D__HEADPHONES_APP_VERSION__="1.3.1") - -target_sources(SonyHeadphonesClient - PRIVATE BluetoothWrapper.cpp - ByteMagic.cpp - CommandSerializer.cpp - TimedMessageQueue.cpp - Listener.cpp - CrossPlatformGUI.cpp "Headphones.cpp") - -target_include_directories (SonyHeadphonesClient - PRIVATE ${CMAKE_SOURCE_DIR} -) - -if(UNIX AND NOT APPLE) - set(LINUX TRUE) -endif() - -if (WIN32 OR LINUX) - set (DEAR_IMGUI TRUE) -endif() - -if (DEAR_IMGUI) - target_sources(SonyHeadphonesClient - PRIVATE CrossPlatformGUI.cpp - imgui/imgui.cpp - imgui/imgui_draw.cpp - imgui/imgui_tables.cpp - imgui/imgui_widgets.cpp - CascadiaCodeFont.cpp - ) - - target_include_directories (SonyHeadphonesClient - PRIVATE ${CMAKE_SOURCE_DIR}/imgui - ) -endif () - -if (LINUX) - target_sources(SonyHeadphonesClient - PRIVATE linux/DBusHelper.cpp - linux/LinuxBluetoothConnector.cpp - linux/LinuxGUI.cpp - linux/main.cpp - imgui/backends/imgui_impl_glfw.cpp - imgui/backends/imgui_impl_opengl3.cpp - ) - - set (CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/linux) - - set (OpenGL_GL_PREFERENCE GLVND) - - find_package(DBus REQUIRED) - - find_package(GLEW REQUIRED) - - find_package(glfw3 REQUIRED) - - find_package(OpenGL REQUIRED) - - if ((NOT DBUS_FOUND) OR (NOT GLEW_FOUND) OR (NOT glfw3_FOUND) OR (NOT OPENGL_FOUND)) - message ( FATAL_ERROR - "Didn't find one of the packages. For Debian based systems, use:" - "sudo apt install libbluetooth-dev libglew-dev libglfw3-dev libdbus-1-dev" ) - endif () - - target_include_directories(SonyHeadphonesClient - PRIVATE ${DBUS_INCLUDE_DIRS} - ${OPENGL_INCLUDE_DIRS} - ) - - target_link_libraries(SonyHeadphonesClient - PRIVATE ${DBUS_LIBRARIES} - GLEW::GLEW - glfw - bluetooth - OpenGL::GL - ) - -endif () - -if (WIN32) - ADD_DEFINITIONS(-DUNICODE) - - target_sources(SonyHeadphonesClient - PRIVATE windows/WindowsGUI.cpp - windows/main.cpp - windows/WindowsBluetoothConnector.cpp - imgui/backends/imgui_impl_dx11.cpp - imgui/backends/imgui_impl_win32.cpp - ) - - target_link_libraries(SonyHeadphonesClient - PRIVATE d3d11 - ) -endif () - -target_link_libraries(SonyHeadphonesClient - PRIVATE Threads::Threads -) +cmake_minimum_required(VERSION 3.1 FATAL_ERROR) +project(SonyHeadphonesClient) + +set(CMAKE_CXX_STANDARD 20) + +find_package(Threads REQUIRED) + +add_executable(SonyHeadphonesClient) + +add_definitions(-D__HEADPHONES_APP_VERSION__="1.3.1") + +target_sources(SonyHeadphonesClient + PRIVATE BluetoothWrapper.cpp + ByteMagic.cpp + CommandSerializer.cpp + TimedMessageQueue.cpp + Listener.cpp + CrossPlatformGUI.cpp "Headphones.cpp") + +target_include_directories (SonyHeadphonesClient + PRIVATE ${CMAKE_SOURCE_DIR} +) + +if(UNIX AND NOT APPLE) + set(LINUX TRUE) +endif() + +if (WIN32 OR LINUX) + set (DEAR_IMGUI TRUE) +endif() + +if (DEAR_IMGUI) + target_sources(SonyHeadphonesClient + PRIVATE CrossPlatformGUI.cpp + imgui/imgui.cpp + imgui/imgui_draw.cpp + imgui/imgui_tables.cpp + imgui/imgui_widgets.cpp + CascadiaCodeFont.cpp + ) + + target_include_directories (SonyHeadphonesClient + PRIVATE ${CMAKE_SOURCE_DIR}/imgui + ) +endif () + +if (LINUX) + target_sources(SonyHeadphonesClient + PRIVATE linux/DBusHelper.cpp + linux/LinuxBluetoothConnector.cpp + linux/LinuxGUI.cpp + linux/main.cpp + imgui/backends/imgui_impl_glfw.cpp + imgui/backends/imgui_impl_opengl3.cpp + ) + + set (CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/linux) + + set (OpenGL_GL_PREFERENCE GLVND) + + find_package(DBus REQUIRED) + + find_package(GLEW REQUIRED) + + find_package(glfw3 REQUIRED) + + find_package(OpenGL REQUIRED) + + if ((NOT DBUS_FOUND) OR (NOT GLEW_FOUND) OR (NOT glfw3_FOUND) OR (NOT OPENGL_FOUND)) + message ( FATAL_ERROR + "Didn't find one of the packages. For Debian based systems, use:" + "sudo apt install libbluetooth-dev libglew-dev libglfw3-dev libdbus-1-dev" ) + endif () + + target_include_directories(SonyHeadphonesClient + PRIVATE ${DBUS_INCLUDE_DIRS} + ${OPENGL_INCLUDE_DIRS} + ) + + target_link_libraries(SonyHeadphonesClient + PRIVATE ${DBUS_LIBRARIES} + GLEW::GLEW + glfw + bluetooth + OpenGL::GL + ) + +endif () + +if (WIN32) + ADD_DEFINITIONS(-DUNICODE) + + target_sources(SonyHeadphonesClient + PRIVATE windows/WindowsGUI.cpp + windows/main.cpp + windows/WindowsBluetoothConnector.cpp + imgui/backends/imgui_impl_dx11.cpp + imgui/backends/imgui_impl_win32.cpp + ) + + target_link_libraries(SonyHeadphonesClient + PRIVATE d3d11 + ) +endif () + +target_link_libraries(SonyHeadphonesClient + PRIVATE Threads::Threads +) diff --git a/Client/CommandSerializer.cpp b/Client/CommandSerializer.cpp index a1cced0..d9cf7d4 100644 --- a/Client/CommandSerializer.cpp +++ b/Client/CommandSerializer.cpp @@ -1,254 +1,254 @@ -#include "CommandSerializer.h" - -/* - * Because - * 0x3E represents beginning of packet - * 0xeC 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 -{ - Buffer _escapeSpecials(const Buffer& src) - { - Buffer ret; - ret.reserve(src.size()); - - for (auto&& b : src) - { - switch (b) - { - case 60: - ret.push_back(ESCAPED_BYTE_SENTRY); - ret.push_back(ESCAPED_60); - break; - - case 61: - ret.push_back(ESCAPED_BYTE_SENTRY); - ret.push_back(ESCAPED_61); - break; - - case 62: - ret.push_back(ESCAPED_BYTE_SENTRY); - ret.push_back(ESCAPED_62); - break; - - default: - ret.push_back(b); - break; - } - } - - return ret; - } - - Buffer _unescapeSpecials(const Buffer& src) - { - Buffer ret; - ret.reserve(src.size()); - - for (size_t i = 0; i < src.size(); i++) - { - auto currByte = src[i]; - if (currByte == ESCAPED_BYTE_SENTRY) - { - if (i == src.size() - 1) - { - throw std::runtime_error("No data left for escaped byte data"); - } - i = i + 1; - switch (src[i]) - { - case ESCAPED_60: - ret.push_back(60); - break; - - case ESCAPED_61: - ret.push_back(61); - break; - - case ESCAPED_62: - ret.push_back(62); - break; - - default: - throw std::runtime_error("Unexpected escaped byte"); - break; - } - } - else - { - ret.push_back(currByte); - } - } - - return ret; - } - - unsigned char _sumChecksum(const char* src, size_t size) - { - unsigned char accumulator = 0; - for (size_t i = 0; i < size; i++) - { - accumulator += src[i]; - } - return accumulator; - } - - unsigned char _sumChecksum(const Buffer& src) - { - return _sumChecksum(src.data(), src.size()); - } - - Buffer packageDataForBt(const Buffer& src, DATA_TYPE dataType, unsigned int seqNumber) - { - //Reserve at least the size for the size, start&end markers, and the source - Buffer toEscape; - toEscape.reserve(src.size() + 2 + sizeof(int)); - Buffer ret; - ret.reserve(toEscape.capacity()); - toEscape.push_back(static_cast(dataType)); - toEscape.push_back(seqNumber); - auto retSize = intToBytesBE(static_cast(src.size())); - //Insert data size - toEscape.insert(toEscape.end(), retSize.begin(), retSize.end()); - //Insert command data - toEscape.insert(toEscape.end(), src.begin(), src.end()); - - auto checksum = _sumChecksum(toEscape); - toEscape.push_back(checksum); - toEscape = _escapeSpecials(toEscape); - - - ret.push_back(START_MARKER); - ret.insert(ret.end(), toEscape.begin(), toEscape.end()); - ret.push_back(END_MARKER); - - - // Message will be chunked if it's larger than MAX_BLUETOOTH_MESSAGE_SIZE, just crash to deal with it for now - if (ret.size() > MAX_BLUETOOTH_MESSAGE_SIZE) - { - throw std::runtime_error("Exceeded the max bluetooth message size, and I can't handle chunked messages"); - } - - return ret; - } - - BtMessage unpackBtMessage(const Buffer& src) - { - //Message data format: ESCAPE_SPECIALS(<1 BYTE CHECKSUM>) - auto unescaped = _unescapeSpecials(src); - - if (unescaped.size() < 7) - { - throw std::runtime_error("Invalid message: Smaller than the minimum message size"); - } - - BtMessage ret; - ret.dataType = static_cast(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(unescaped[5]); - ret.messageBytes.insert(ret.messageBytes.end(), unescaped.begin() + 6, unescaped.begin() + 6 + numMsgBytes); - return ret; - } - - NC_DUAL_SINGLE_VALUE getDualSingleForAsmLevel(char asmLevel) - { - NC_DUAL_SINGLE_VALUE val = NC_DUAL_SINGLE_VALUE::OFF; - if (asmLevel > MAX_STEPS_WH_1000_XM3) - { - throw std::runtime_error("Exceeded max steps"); - } - else if (asmLevel == 1) - { - val = NC_DUAL_SINGLE_VALUE::SINGLE; - } - else if (asmLevel == 0) - { - val = NC_DUAL_SINGLE_VALUE::DUAL; - } - return val; - } - - Buffer serializeXM4OptimizeCommand(OPTIMIZER_STATE state) - { - Buffer ret; - ret.push_back(static_cast(COMMAND_TYPE::XM4_OPTIMIZER_PARAM)); - ret.push_back(static_cast(0x01)); - ret.push_back(static_cast(0x00)); - ret.push_back(static_cast(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; - ret.push_back(static_cast(COMMAND_TYPE::NCASM_SET_PARAM)); - ret.push_back(static_cast(NC_ASM_INQUIRED_TYPE::NOISE_CANCELLING_AND_AMBIENT_SOUND_MODE)); - ret.push_back(static_cast(ncAsmEffect)); - ret.push_back(static_cast(ncAsmSettingType)); - ret.push_back(static_cast(getDualSingleForAsmLevel(asmLevel))); - ret.push_back(static_cast(asmSettingType)); - ret.push_back(static_cast(asmId)); - ret.push_back(asmLevel); - return ret; - } - - Buffer serializeVPTSetting(VPT_INQUIRED_TYPE type, unsigned char preset) - { - Buffer ret; - ret.push_back(static_cast(COMMAND_TYPE::VPT_SET_PARAM)); - ret.push_back(static_cast(type)); - ret.push_back(preset); - - return ret; - } - - Buffer serializeXM4SpeakToChat(S2C_TOGGLE s2cState) - { - Buffer ret; - ret.push_back(static_cast(COMMAND_TYPE::XM4_S2C_TOGGLE_PARAM)); - ret.push_back(static_cast(0x05)); - ret.push_back(static_cast(0x01)); - ret.push_back(static_cast(s2cState)); - return ret; - } - - Buffer serializeXM4_S2C_Options(unsigned char sensitivity, unsigned char voice, unsigned char offTime) - { - Buffer ret; - ret.push_back(static_cast(COMMAND_TYPE::XM4_S2C_OPTIONS_PARAM)); - ret.push_back(static_cast(0x05)); - ret.push_back(static_cast(0x00)); - ret.push_back(static_cast(sensitivity)); - ret.push_back(static_cast(voice)); - ret.push_back(static_cast(offTime)); - return ret; - } - - Buffer serializeMultiPointCommand(MULTI_POINT_COMMANDS cmd, std::string macAddr) - { - Buffer ret; - ret.push_back(static_cast(COMMAND_TYPE::MULTI_POINT_PARAM)); - ret.push_back(static_cast(0x01)); - ret.push_back(static_cast(0x00)); - ret.push_back(static_cast(cmd)); - for (unsigned char c: macAddr) - { - ret.push_back(c); - } - return ret; - } -} - +#include "CommandSerializer.h" + +/* + * Because + * 0x3E represents beginning of packet + * 0xeC 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 +{ + Buffer _escapeSpecials(const Buffer& src) + { + Buffer ret; + ret.reserve(src.size()); + + for (auto&& b : src) + { + switch (b) + { + case 60: + ret.push_back(ESCAPED_BYTE_SENTRY); + ret.push_back(ESCAPED_60); + break; + + case 61: + ret.push_back(ESCAPED_BYTE_SENTRY); + ret.push_back(ESCAPED_61); + break; + + case 62: + ret.push_back(ESCAPED_BYTE_SENTRY); + ret.push_back(ESCAPED_62); + break; + + default: + ret.push_back(b); + break; + } + } + + return ret; + } + + Buffer _unescapeSpecials(const Buffer& src) + { + Buffer ret; + ret.reserve(src.size()); + + for (size_t i = 0; i < src.size(); i++) + { + auto currByte = src[i]; + if (currByte == ESCAPED_BYTE_SENTRY) + { + if (i == src.size() - 1) + { + throw std::runtime_error("No data left for escaped byte data"); + } + i = i + 1; + switch (src[i]) + { + case ESCAPED_60: + ret.push_back(60); + break; + + case ESCAPED_61: + ret.push_back(61); + break; + + case ESCAPED_62: + ret.push_back(62); + break; + + default: + throw std::runtime_error("Unexpected escaped byte"); + break; + } + } + else + { + ret.push_back(currByte); + } + } + + return ret; + } + + unsigned char _sumChecksum(const char* src, size_t size) + { + unsigned char accumulator = 0; + for (size_t i = 0; i < size; i++) + { + accumulator += src[i]; + } + return accumulator; + } + + unsigned char _sumChecksum(const Buffer& src) + { + return _sumChecksum(src.data(), src.size()); + } + + Buffer packageDataForBt(const Buffer& src, DATA_TYPE dataType, unsigned int seqNumber) + { + //Reserve at least the size for the size, start&end markers, and the source + Buffer toEscape; + toEscape.reserve(src.size() + 2 + sizeof(int)); + Buffer ret; + ret.reserve(toEscape.capacity()); + toEscape.push_back(static_cast(dataType)); + toEscape.push_back(seqNumber); + auto retSize = intToBytesBE(static_cast(src.size())); + //Insert data size + toEscape.insert(toEscape.end(), retSize.begin(), retSize.end()); + //Insert command data + toEscape.insert(toEscape.end(), src.begin(), src.end()); + + auto checksum = _sumChecksum(toEscape); + toEscape.push_back(checksum); + toEscape = _escapeSpecials(toEscape); + + + ret.push_back(START_MARKER); + ret.insert(ret.end(), toEscape.begin(), toEscape.end()); + ret.push_back(END_MARKER); + + + // Message will be chunked if it's larger than MAX_BLUETOOTH_MESSAGE_SIZE, just crash to deal with it for now + if (ret.size() > MAX_BLUETOOTH_MESSAGE_SIZE) + { + throw std::runtime_error("Exceeded the max bluetooth message size, and I can't handle chunked messages"); + } + + return ret; + } + + BtMessage unpackBtMessage(const Buffer& src) + { + //Message data format: ESCAPE_SPECIALS(<1 BYTE CHECKSUM>) + auto unescaped = _unescapeSpecials(src); + + if (unescaped.size() < 7) + { + throw std::runtime_error("Invalid message: Smaller than the minimum message size"); + } + + BtMessage ret; + ret.dataType = static_cast(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(unescaped[5]); + ret.messageBytes.insert(ret.messageBytes.end(), unescaped.begin() + 6, unescaped.begin() + 6 + numMsgBytes); + return ret; + } + + NC_DUAL_SINGLE_VALUE getDualSingleForAsmLevel(char asmLevel) + { + NC_DUAL_SINGLE_VALUE val = NC_DUAL_SINGLE_VALUE::OFF; + if (asmLevel > MAX_STEPS_WH_1000_XM3) + { + throw std::runtime_error("Exceeded max steps"); + } + else if (asmLevel == 1) + { + val = NC_DUAL_SINGLE_VALUE::SINGLE; + } + else if (asmLevel == 0) + { + val = NC_DUAL_SINGLE_VALUE::DUAL; + } + return val; + } + + Buffer serializeXM4OptimizeCommand(OPTIMIZER_STATE state) + { + Buffer ret; + ret.push_back(static_cast(COMMAND_TYPE::XM4_OPTIMIZER_PARAM)); + ret.push_back(static_cast(0x01)); + ret.push_back(static_cast(0x00)); + ret.push_back(static_cast(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; + ret.push_back(static_cast(COMMAND_TYPE::NCASM_SET_PARAM)); + ret.push_back(static_cast(NC_ASM_INQUIRED_TYPE::NOISE_CANCELLING_AND_AMBIENT_SOUND_MODE)); + ret.push_back(static_cast(ncAsmEffect)); + ret.push_back(static_cast(ncAsmSettingType)); + ret.push_back(static_cast(getDualSingleForAsmLevel(asmLevel))); + ret.push_back(static_cast(asmSettingType)); + ret.push_back(static_cast(asmId)); + ret.push_back(asmLevel); + return ret; + } + + Buffer serializeVPTSetting(VPT_INQUIRED_TYPE type, unsigned char preset) + { + Buffer ret; + ret.push_back(static_cast(COMMAND_TYPE::VPT_SET_PARAM)); + ret.push_back(static_cast(type)); + ret.push_back(preset); + + return ret; + } + + Buffer serializeXM4SpeakToChat(S2C_TOGGLE s2cState) + { + Buffer ret; + ret.push_back(static_cast(COMMAND_TYPE::XM4_S2C_TOGGLE_PARAM)); + ret.push_back(static_cast(0x05)); + ret.push_back(static_cast(0x01)); + ret.push_back(static_cast(s2cState)); + return ret; + } + + Buffer serializeXM4_S2C_Options(unsigned char sensitivity, unsigned char voice, unsigned char offTime) + { + Buffer ret; + ret.push_back(static_cast(COMMAND_TYPE::XM4_S2C_OPTIONS_PARAM)); + ret.push_back(static_cast(0x05)); + ret.push_back(static_cast(0x00)); + ret.push_back(static_cast(sensitivity)); + ret.push_back(static_cast(voice)); + ret.push_back(static_cast(offTime)); + return ret; + } + + Buffer serializeMultiPointCommand(MULTI_POINT_COMMANDS cmd, std::string macAddr) + { + Buffer ret; + ret.push_back(static_cast(COMMAND_TYPE::MULTI_POINT_PARAM)); + ret.push_back(static_cast(0x01)); + ret.push_back(static_cast(0x00)); + ret.push_back(static_cast(cmd)); + for (unsigned char c: macAddr) + { + ret.push_back(c); + } + return ret; + } +} + diff --git a/Client/CommandSerializer.h b/Client/CommandSerializer.h index 73da494..3538ff4 100644 --- a/Client/CommandSerializer.h +++ b/Client/CommandSerializer.h @@ -1,50 +1,50 @@ -#pragma once -#include "Constants.h" -#include "ByteMagic.h" -#include -#include -#include -#include "Exceptions.h" - -constexpr int MINIMUM_VOICE_FOCUS_STEP = 2; -constexpr unsigned int ASM_LEVEL_DISABLED = -1; - -struct BtMessage -{ - DATA_TYPE dataType; - unsigned char seqNumber; - //Not really needed for now - Buffer messageBytes; -}; - - -namespace CommandSerializer -{ - //escape special chars - - Buffer _escapeSpecials(const Buffer& src); - Buffer _unescapeSpecials(const Buffer& src); - unsigned char _sumChecksum(const char* src, size_t size); - unsigned char _sumChecksum(const Buffer& src); - //Package a serialized command according to the protocol - /* - References: - * DataType - * CommandBluetoothSender.sendCommandWithRetries - * BluetoothSenderWrapper.sendCommandViaBluetooth - * - * Serialized data format: ESCAPE_SPECIALS(<1 BYTE CHECKSUM>) - */ - Buffer packageDataForBt(const Buffer& src, DATA_TYPE dataType, unsigned int seqNumber); - - 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); -} - +#pragma once +#include "Constants.h" +#include "ByteMagic.h" +#include +#include +#include +#include "Exceptions.h" + +constexpr int MINIMUM_VOICE_FOCUS_STEP = 2; +constexpr unsigned int ASM_LEVEL_DISABLED = -1; + +struct BtMessage +{ + DATA_TYPE dataType; + unsigned char seqNumber; + //Not really needed for now + Buffer messageBytes; +}; + + +namespace CommandSerializer +{ + //escape special chars + + Buffer _escapeSpecials(const Buffer& src); + Buffer _unescapeSpecials(const Buffer& src); + unsigned char _sumChecksum(const char* src, size_t size); + unsigned char _sumChecksum(const Buffer& src); + //Package a serialized command according to the protocol + /* + References: + * DataType + * CommandBluetoothSender.sendCommandWithRetries + * BluetoothSenderWrapper.sendCommandViaBluetooth + * + * Serialized data format: ESCAPE_SPECIALS(<1 BYTE CHECKSUM>) + */ + Buffer packageDataForBt(const Buffer& src, DATA_TYPE dataType, unsigned int seqNumber); + + 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); +} + diff --git a/Client/Constants.h b/Client/Constants.h index 9954ae5..6caf812 100644 --- a/Client/Constants.h +++ b/Client/Constants.h @@ -1,167 +1,167 @@ -#pragma once - -#include - -inline constexpr auto MAX_BLUETOOTH_MESSAGE_SIZE = 2048; -inline constexpr char START_MARKER{ 62 }; -inline constexpr char END_MARKER{ 60 }; - -inline constexpr auto MAC_ADDR_STR_SIZE = 17; - -inline constexpr auto SERVICE_UUID = "96CC203E-5068-46ad-B32D-E316F5E069BA"; -inline unsigned char SERVICE_UUID_IN_BYTES[] = { // this is the SERVICE_UUID but in bytes - 0x96, 0xcc, 0x20, 0x3e, 0x50, 0x68, 0x46, 0xad, - 0xb3, 0x2d, 0xe3, 0x16, 0xf5, 0xe0, 0x69, 0xba -}; - -#define APP_NAME "Sony Headphones App v" __HEADPHONES_APP_VERSION__ -#define APP_NAME_W (L"" APP_NAME) - -using Buffer = std::vector; - -enum class DATA_TYPE : signed char -{ - DATA = 0, - ACK = 1, - DATA_MC_NO1 = 2, - DATA_ICD = 9, - DATA_EV = 10, - DATA_MDR = 12, - DATA_COMMON = 13, - DATA_MDR_NO2 = 14, - SHOT = 16, - SHOT_MC_NO1 = 18, - SHOT_ICD = 25, - SHOT_EV = 26, - SHOT_MDR = 28, - SHOT_COMMON = 29, - SHOT_MDR_NO2 = 30, - LARGE_DATA_COMMON = 45, - UNKNOWN = -1 -}; - - -enum class NC_ASM_INQUIRED_TYPE : signed char -{ - NO_USE = 0, - NOISE_CANCELLING = 1, - NOISE_CANCELLING_AND_AMBIENT_SOUND_MODE = 2, - AMBIENT_SOUND_MODE = 3 -}; - -enum class NC_ASM_EFFECT : signed char -{ - OFF = 0, - ON = 1, - ADJUSTMENT_IN_PROGRESS = 16, - ADJUSTMENT_COMPLETION = 17 -}; - -enum class NC_ASM_SETTING_TYPE : signed char -{ - ON_OFF = 0, - LEVEL_ADJUSTMENT = 1, - DUAL_SINGLE_OFF = 2 -}; - -enum class ASM_SETTING_TYPE : signed char -{ - ON_OFF = 0, - LEVEL_ADJUSTMENT = 1 -}; - -enum class ASM_ID : signed char -{ - NORMAL = 0, - VOICE = 1 -}; - -enum class NC_DUAL_SINGLE_VALUE : signed char -{ - OFF = 0, - SINGLE = 1, - DUAL = 2 -}; - -enum class COMMAND_TYPE : signed char -{ - VPT_SET_PARAM = 72, - 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, - MULTI_POINT_SETTING_RESPONSE = (signed char) 0x07, - - MULTI_POINT_SETTING_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 -{ - OFF = 0, - OUTDOOR_FESTIVAL = 1, - ARENA = 2, - CONCERT_HALL = 3, - CLUB = 4 - //Note: Sony reserved 5~15 "for future" -}; - -enum class SOUND_POSITION_PRESET : signed char -{ - OFF = 0, - FRONT_LEFT = 1, - FRONT_RIGHT = 2, - FRONT = 3, - REAR_LEFT = 17, - REAR_RIGHT = 18, - OUT_OF_RANGE = -1 -}; - -//Needed for converting the ImGui Combo index into the VPT index. -inline const SOUND_POSITION_PRESET SOUND_POSITION_PRESET_ARRAY[] = { - SOUND_POSITION_PRESET::OFF, - SOUND_POSITION_PRESET::FRONT_LEFT, - SOUND_POSITION_PRESET::FRONT_RIGHT, - SOUND_POSITION_PRESET::FRONT, - SOUND_POSITION_PRESET::REAR_LEFT, - SOUND_POSITION_PRESET::REAR_RIGHT, - SOUND_POSITION_PRESET::OUT_OF_RANGE -}; - -enum class VPT_INQUIRED_TYPE : signed char -{ - NO_USE = 0, - VPT = 1, - 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 QUERY_RESPONSE_TYPE : signed char -{ - DEVICES = (signed char) 0x37, -}; - -enum class MULTI_POINT_COMMANDS : signed char -{ - CONNECT = (signed char) 0x01, - DISCONNECT = (signed char) 0x00 -}; +#pragma once + +#include + +inline constexpr auto MAX_BLUETOOTH_MESSAGE_SIZE = 2048; +inline constexpr char START_MARKER{ 62 }; +inline constexpr char END_MARKER{ 60 }; + +inline constexpr auto MAC_ADDR_STR_SIZE = 17; + +inline constexpr auto SERVICE_UUID = "96CC203E-5068-46ad-B32D-E316F5E069BA"; +inline unsigned char SERVICE_UUID_IN_BYTES[] = { // this is the SERVICE_UUID but in bytes + 0x96, 0xcc, 0x20, 0x3e, 0x50, 0x68, 0x46, 0xad, + 0xb3, 0x2d, 0xe3, 0x16, 0xf5, 0xe0, 0x69, 0xba +}; + +#define APP_NAME "Sony Headphones App v" __HEADPHONES_APP_VERSION__ +#define APP_NAME_W (L"" APP_NAME) + +using Buffer = std::vector; + +enum class DATA_TYPE : signed char +{ + DATA = 0, + ACK = 1, + DATA_MC_NO1 = 2, + DATA_ICD = 9, + DATA_EV = 10, + DATA_MDR = 12, + DATA_COMMON = 13, + DATA_MDR_NO2 = 14, + SHOT = 16, + SHOT_MC_NO1 = 18, + SHOT_ICD = 25, + SHOT_EV = 26, + SHOT_MDR = 28, + SHOT_COMMON = 29, + SHOT_MDR_NO2 = 30, + LARGE_DATA_COMMON = 45, + UNKNOWN = -1 +}; + + +enum class NC_ASM_INQUIRED_TYPE : signed char +{ + NO_USE = 0, + NOISE_CANCELLING = 1, + NOISE_CANCELLING_AND_AMBIENT_SOUND_MODE = 2, + AMBIENT_SOUND_MODE = 3 +}; + +enum class NC_ASM_EFFECT : signed char +{ + OFF = 0, + ON = 1, + ADJUSTMENT_IN_PROGRESS = 16, + ADJUSTMENT_COMPLETION = 17 +}; + +enum class NC_ASM_SETTING_TYPE : signed char +{ + ON_OFF = 0, + LEVEL_ADJUSTMENT = 1, + DUAL_SINGLE_OFF = 2 +}; + +enum class ASM_SETTING_TYPE : signed char +{ + ON_OFF = 0, + LEVEL_ADJUSTMENT = 1 +}; + +enum class ASM_ID : signed char +{ + NORMAL = 0, + VOICE = 1 +}; + +enum class NC_DUAL_SINGLE_VALUE : signed char +{ + OFF = 0, + SINGLE = 1, + DUAL = 2 +}; + +enum class COMMAND_TYPE : signed char +{ + VPT_SET_PARAM = 72, + 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, + MULTI_POINT_SETTING_RESPONSE = (signed char) 0x07, + + MULTI_POINT_SETTING_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 +{ + OFF = 0, + OUTDOOR_FESTIVAL = 1, + ARENA = 2, + CONCERT_HALL = 3, + CLUB = 4 + //Note: Sony reserved 5~15 "for future" +}; + +enum class SOUND_POSITION_PRESET : signed char +{ + OFF = 0, + FRONT_LEFT = 1, + FRONT_RIGHT = 2, + FRONT = 3, + REAR_LEFT = 17, + REAR_RIGHT = 18, + OUT_OF_RANGE = -1 +}; + +//Needed for converting the ImGui Combo index into the VPT index. +inline const SOUND_POSITION_PRESET SOUND_POSITION_PRESET_ARRAY[] = { + SOUND_POSITION_PRESET::OFF, + SOUND_POSITION_PRESET::FRONT_LEFT, + SOUND_POSITION_PRESET::FRONT_RIGHT, + SOUND_POSITION_PRESET::FRONT, + SOUND_POSITION_PRESET::REAR_LEFT, + SOUND_POSITION_PRESET::REAR_RIGHT, + SOUND_POSITION_PRESET::OUT_OF_RANGE +}; + +enum class VPT_INQUIRED_TYPE : signed char +{ + NO_USE = 0, + VPT = 1, + 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 QUERY_RESPONSE_TYPE : signed char +{ + DEVICES = (signed char) 0x37, +}; + +enum class MULTI_POINT_COMMANDS : signed char +{ + CONNECT = (signed char) 0x01, + DISCONNECT = (signed char) 0x00 +}; diff --git a/Client/CrossPlatformGUI.cpp b/Client/CrossPlatformGUI.cpp index d9214b0..97f6507 100644 --- a/Client/CrossPlatformGUI.cpp +++ b/Client/CrossPlatformGUI.cpp @@ -1,425 +1,425 @@ -#include "CrossPlatformGUI.h" - -bool CrossPlatformGUI::performGUIPass() -{ - ImGui::NewFrame(); - - static bool isConnected = false; - - bool open = true; - - ImGui::SetNextWindowPos({ 0,0 }); - - { - //TODO: Figure out how to get rid of the Windows window, make everything transparent, and just use ImGui for everything. - //TODO: ImGuiWindowFlags_AlwaysAutoResize causes some flickering. Figure out how to stop it - ImGui::Begin("Sony Headphones", &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar); - - //Legal disclaimer - ImGui::Text("! This product is not affiliated with Sony. Use at your own risk. !"); - ImGui::Text("Source: https://github.com/Plutoberth/SonyHeadphonesClient"); - ImGui::Spacing(); - - this->_drawErrors(); - this->_drawDeviceDiscovery(); - - if (this->_bt.isConnected()) - { - // ImGui::Spacing(); - ImGui::Separator(); - this->_drawASMControls(); - if (this->_bt.isConnected() && (this->_connectedDevice.name) == "WH-1000XM4"){ - this->_drawSpeakToChat(); - if (this->_headphones.getMultiPointSetting()) - this->_drawMultiPointConn(); - ImGui::Separator(); - this->_drawOptimizerButton(); - } - else { - this->_drawSurroundControls(); - } - - this->_setHeadphoneSettings(); - } - } - - ImGui::End(); - ImGui::Render(); - - return open; -} - -void CrossPlatformGUI::_drawErrors() -{ - //There's a slight race condition here but I don't care, it'd only be for one frame. - if (this->_mq.begin() != this->_mq.end()) - { - ImGui::Text("Errors:"); - ImGui::Spacing(); - - for (auto&& message : this->_mq) - { - ImGui::Text(message.message.c_str()); - } - - ImGui::Spacing(); - } -} - -void CrossPlatformGUI::_drawDeviceDiscovery() -{ - if (ImGui::CollapsingHeader("Device Discovery ", ImGuiTreeNodeFlags_DefaultOpen)) - { - static std::vector connectedDevices; - static int selectedDevice = -1; - - if (this->_bt.isConnected()) - { - ImGui::Text("Connected to %s", this->_connectedDevice.name.c_str()); - if (ImGui::Button("Disconnect")) - { - selectedDevice = -1; - this->_bt.disconnect(); - } - } - else - { - ImGui::Text("Select from one of the available devices: "); - - int temp = 0; - for (const auto& device : connectedDevices) - { - ImGui::RadioButton(device.name.c_str(), &selectedDevice, temp++); - } - - ImGui::Spacing(); - - if (this->_connectFuture.valid()) - { - if (this->_connectFuture.ready()) - { - try - { - this->_connectFuture.get(); - } - catch (const RecoverableException& exc) - { - if (exc.shouldDisconnect) - { - this->_bt.disconnect(); - } - this->_mq.addMessage(exc.what()); - } - } - else - { - ImGui::Text("Connecting %c", "|/-\\"[(int)(ImGui::GetTime() / 0.05f) & 3]); - } - } - else - { - if (ImGui::Button("Connect")) - { - if (selectedDevice != -1) - { - this->_connectedDevice = connectedDevices[selectedDevice]; - this->_connectFuture.setFromAsync([this]() { - this->_bt.connect(this->_connectedDevice.mac); - - // Add post-connection setup here - this->_listener = std::make_unique(this->_headphones, this->_bt); - auto useless_future = std::async(std::launch::async, &Listener::listen, this->_listener.get()); - if (this->_connectedDevice.name == "WH-1000XM4") - this->_headphones.queryState(); - } - ); - } - } - } - - ImGui::SameLine(); - - if (this->_connectedDevicesFuture.valid()) - { - if (this->_connectedDevicesFuture.ready()) - { - try - { - connectedDevices = this->_connectedDevicesFuture.get(); - } - catch (const RecoverableException& exc) - { - if (exc.shouldDisconnect) - { - this->_bt.disconnect(); - } - this->_mq.addMessage(exc.what()); - } - } - else - { - ImGui::Text("Discovering Devices %c", "|/-\\"[(int)(ImGui::GetTime() / 0.05f) & 3]); - } - } - else - { - if (ImGui::Button("Refresh devices")) - { - selectedDevice = -1; - this->_connectedDevicesFuture.setFromAsync([this]() { return this->_bt.getConnectedDevices(); }); - } - } - } - } -} - -void CrossPlatformGUI::_drawASMControls() -{ - static bool ambientSoundControl = true; - static bool focusOnVoice = false; - static int asmLevel = 0; - - if (ImGui::CollapsingHeader("Ambient Sound Mode ", ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::Checkbox("Ambient Sound Control", &ambientSoundControl); - - if (this->_headphones.isSetAsmLevelAvailable()) - { - ImGui::Text("Control ambient sound for your %ss", this->_connectedDevice.name.c_str()); - - ImGui::SliderInt("Ambient Sound Level", &asmLevel, 0, 19); - - if (this->_headphones.isFocusOnVoiceAvailable()) - { - ImGui::Checkbox("Focus on Voice", &focusOnVoice); - } - else - { - ImGui::Text("Focus on Voice isn't enabled on this level."); - } - } - - this->_headphones.setAmbientSoundControl(ambientSoundControl); - this->_headphones.setAsmLevel(asmLevel); - this->_headphones.setFocusOnVoice(focusOnVoice); - } -} - -void CrossPlatformGUI::_drawSurroundControls() -{ - static int soundPosition = 0; - static int vptType = 0; - - if (ImGui::CollapsingHeader("Virtual Sound", ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::Text("Only one of the options may be used at a time"); - - if (ImGui::Combo("Sound Position", &soundPosition, "Off\0Front Left\0Front Right\0" - "Front\0Rear Left\0Rear Right\0\0")) - { - vptType = 0; - } - - if (ImGui::Combo("Surround (VPT)", &vptType, "Off\0Outdoor Festival\0Arena\0" - "Concert Hall\0Club\0\0")) - { - soundPosition = 0; - } - - this->_headphones.setSurroundPosition(SOUND_POSITION_PRESET_ARRAY[soundPosition]); - this->_headphones.setVptType(vptType); - } -} - -void CrossPlatformGUI::_drawOptimizerButton() -{ - if (this->_headphones.getOptimizerState() == OPTIMIZER_STATE::IDLE) - { - if (ImGui::Button("Optimize")) - { - this->_headphones.setOptimizerState(OPTIMIZER_STATE::OPTIMIZING); - } - } - else { - // TODO: Change button to show cancel option while optimizing state - if (ImGui::Button("Optimizing... (Press To Cancel)")) - { - this->_headphones.setOptimizerState(OPTIMIZER_STATE::IDLE); - } - } -} - -void CrossPlatformGUI::_drawSpeakToChat() -{ - enum Sensitivity { Sens_Auto, Sens_High, Sens_Low, Sens_count}; - const char* Sens_hints[Sens_count] = { "Auto", "High", "Low" }; - - enum AutoOff { Time_Short, Time_Std, Time_Long, Time_Inf, Time_count}; - const char* Time_hints[Time_count] = { "Short", "Standard", "Long", "Off" }; - - static bool S2Ctoggle_check = false; - static int S2C_Sens = Sens_Auto; - static bool S2C_Voice = 0; - static int S2C_AutoOff = Time_Short; - - const char* Sens_name = (S2C_Sens >= 0 && S2C_Sens < Sens_count) ? Sens_hints[S2C_Sens] : "Unknown"; - const char* Time_name = (S2C_AutoOff >= 0 && S2C_AutoOff < Time_count) ? Time_hints[S2C_AutoOff] : "Unknown"; - - if (ImGui::CollapsingHeader("Speak To Chat Controls", ImGuiTreeNodeFlags_DefaultOpen)) - { - ImGui::Checkbox("Speak To Chat", &S2Ctoggle_check); - - if (S2Ctoggle_check) - { - if (S2Ctoggle_check){ - // ImGui::SameLine(); - ImGui::SliderInt("Speak to chat sensitivity", &S2C_Sens, 0, Sens_count - 1, Sens_name); - ImGui::SliderInt("Speak to chat Auto close", &S2C_AutoOff, 0, Time_count - 1, Time_name); - ImGui::Checkbox("Voice passthrough", &S2C_Voice); - } - else { - } - } - } - - if (S2Ctoggle_check) - this->_headphones.setS2CToggle(S2C_TOGGLE::ACTIVE); - else - this->_headphones.setS2CToggle(S2C_TOGGLE::INACTIVE); - - this->_headphones.setS2COptions(S2C_Sens, S2C_Voice, S2C_AutoOff); - -} - -void CrossPlatformGUI::_drawMultiPointConn() -{ - auto devices = this->_headphones.getDevices(); - auto connectedDevices = this->_headphones.getConnectedDevices(); - - int dev1 = connectedDevices.first; - int dev2 = connectedDevices.second; - int old_dev1 = dev1; - int old_dev2 = dev2; - - if(ImGui::CollapsingHeader("Connected Devices", ImGuiTreeNodeFlags_DefaultOpen)) - { - if (devices[dev1].name!="") - { - ImGui::Text(devices[dev1].name.c_str()); - ImGui::SameLine(); - if (ImGui::Button("Disconnect device 1")) - { - this->_headphones.setMultiPointConnection(1, 0, dev1); - } - } - - if (devices[dev1].name=="" || devices[dev2].name=="") - { - // const char* combo_preview_value = devices[dev1].name // &(devices[dev1].name); - if (ImGui::BeginCombo("Connect", "")) - { - for (int i = 0; i < devices.size(); i++) - { - if (i==dev2 || i==dev1) - continue; - - const bool is_selected = false; - if (ImGui::Selectable(devices[i].name.c_str(), is_selected)) - { - if (devices[dev1].name=="") - { - dev1 = i; - this->_headphones.setMultiPointConnection(1, dev1, 0); - } - else { - dev2 = i; - this->_headphones.setMultiPointConnection(2, dev2, 0); - } - } - - // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) - if (is_selected) - ImGui::SetItemDefaultFocus(); - } - ImGui::EndCombo(); - } - - ImGui::SameLine(); - if (devices[dev1].name=="") - ImGui::Text("Device 1"); - else - ImGui::Text("Device 2"); - } - - if (devices[dev2].name!="") - { - ImGui::Text(devices[dev2].name.c_str()); - ImGui::SameLine(); - if (ImGui::Button("Disconnect device 2")) - { - this->_headphones.setMultiPointConnection(2,0,dev2); - } - } - - } - -} - -void CrossPlatformGUI::_setHeadphoneSettings() { - //Don't show if the command only takes a few frames to send - static int commandLinger = 0; - - if (this->_sendCommandFuture.ready()) - { - commandLinger = 0; - try - { - this->_sendCommandFuture.get(); - } - catch (const RecoverableException& exc) - { - std::string excString; - //We kinda have to do it here and not in the wrapper, due to async causing timing issues. To fix it, the messagequeue can be made - //static, but I'm not sure if I wanna do that. - if (exc.shouldDisconnect) - { - this->_bt.disconnect(); - excString = "Disconnected due to: "; - } - this->_mq.addMessage(excString + exc.what()); - } - } - //This means that we're waiting - else if (this->_sendCommandFuture.valid()) - { - if (commandLinger++ > (FPS / 10)) - { - ImGui::Text("Sending command %c", "|/-\\"[(int)(ImGui::GetTime() / 0.05f) & 3]); - } - } - //We're not waiting, and there's no command in the air, so we can evaluate sending a new command - else if (this->_headphones.isChanged()) - { - this->_sendCommandFuture.setFromAsync([=, this]() { - return this->_headphones.setChanges(); - }); - } -} - -CrossPlatformGUI::CrossPlatformGUI(BluetoothWrapper bt) : _bt(std::move(bt)), _headphones(_bt) -{ - // Setup Dear ImGui style - ImGui::StyleColorsDark(); - ImGuiIO& io = ImGui::GetIO(); - this->_mq = TimedMessageQueue(GUI_MAX_MESSAGES); - this->_connectedDevicesFuture.setFromAsync([this]() { return this->_bt.getConnectedDevices(); }); - - io.IniFilename = nullptr; - io.WantSaveIniSettings = false; - - //AddFontFromMemory will own the pointer, so there's no leak - char* fileData = new char[sizeof(CascadiaCodeTTF)]; - memcpy(fileData, CascadiaCodeTTF, sizeof(CascadiaCodeTTF)); - ImFont* font = io.Fonts->AddFontFromMemoryTTF(reinterpret_cast(fileData), sizeof(CascadiaCodeTTF), FONT_SIZE); - IM_ASSERT(font != NULL); -} +#include "CrossPlatformGUI.h" + +bool CrossPlatformGUI::performGUIPass() +{ + ImGui::NewFrame(); + + static bool isConnected = false; + + bool open = true; + + ImGui::SetNextWindowPos({ 0,0 }); + + { + //TODO: Figure out how to get rid of the Windows window, make everything transparent, and just use ImGui for everything. + //TODO: ImGuiWindowFlags_AlwaysAutoResize causes some flickering. Figure out how to stop it + ImGui::Begin("Sony Headphones", &open, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar); + + //Legal disclaimer + ImGui::Text("! This product is not affiliated with Sony. Use at your own risk. !"); + ImGui::Text("Source: https://github.com/Plutoberth/SonyHeadphonesClient"); + ImGui::Spacing(); + + this->_drawErrors(); + this->_drawDeviceDiscovery(); + + if (this->_bt.isConnected()) + { + // ImGui::Spacing(); + ImGui::Separator(); + this->_drawASMControls(); + if (this->_bt.isConnected() && (this->_connectedDevice.name) == "WH-1000XM4"){ + this->_drawSpeakToChat(); + if (this->_headphones.getMultiPointSetting()) + this->_drawMultiPointConn(); + ImGui::Separator(); + this->_drawOptimizerButton(); + } + else { + this->_drawSurroundControls(); + } + + this->_setHeadphoneSettings(); + } + } + + ImGui::End(); + ImGui::Render(); + + return open; +} + +void CrossPlatformGUI::_drawErrors() +{ + //There's a slight race condition here but I don't care, it'd only be for one frame. + if (this->_mq.begin() != this->_mq.end()) + { + ImGui::Text("Errors:"); + ImGui::Spacing(); + + for (auto&& message : this->_mq) + { + ImGui::Text(message.message.c_str()); + } + + ImGui::Spacing(); + } +} + +void CrossPlatformGUI::_drawDeviceDiscovery() +{ + if (ImGui::CollapsingHeader("Device Discovery ", ImGuiTreeNodeFlags_DefaultOpen)) + { + static std::vector connectedDevices; + static int selectedDevice = -1; + + if (this->_bt.isConnected()) + { + ImGui::Text("Connected to %s", this->_connectedDevice.name.c_str()); + if (ImGui::Button("Disconnect")) + { + selectedDevice = -1; + this->_bt.disconnect(); + } + } + else + { + ImGui::Text("Select from one of the available devices: "); + + int temp = 0; + for (const auto& device : connectedDevices) + { + ImGui::RadioButton(device.name.c_str(), &selectedDevice, temp++); + } + + ImGui::Spacing(); + + if (this->_connectFuture.valid()) + { + if (this->_connectFuture.ready()) + { + try + { + this->_connectFuture.get(); + } + catch (const RecoverableException& exc) + { + if (exc.shouldDisconnect) + { + this->_bt.disconnect(); + } + this->_mq.addMessage(exc.what()); + } + } + else + { + ImGui::Text("Connecting %c", "|/-\\"[(int)(ImGui::GetTime() / 0.05f) & 3]); + } + } + else + { + if (ImGui::Button("Connect")) + { + if (selectedDevice != -1) + { + this->_connectedDevice = connectedDevices[selectedDevice]; + this->_connectFuture.setFromAsync([this]() { + this->_bt.connect(this->_connectedDevice.mac); + + // Add post-connection setup here + this->_listener = std::make_unique(this->_headphones, this->_bt); + auto useless_future = std::async(std::launch::async, &Listener::listen, this->_listener.get()); + if (this->_connectedDevice.name == "WH-1000XM4") + this->_headphones.queryState(); + } + ); + } + } + } + + ImGui::SameLine(); + + if (this->_connectedDevicesFuture.valid()) + { + if (this->_connectedDevicesFuture.ready()) + { + try + { + connectedDevices = this->_connectedDevicesFuture.get(); + } + catch (const RecoverableException& exc) + { + if (exc.shouldDisconnect) + { + this->_bt.disconnect(); + } + this->_mq.addMessage(exc.what()); + } + } + else + { + ImGui::Text("Discovering Devices %c", "|/-\\"[(int)(ImGui::GetTime() / 0.05f) & 3]); + } + } + else + { + if (ImGui::Button("Refresh devices")) + { + selectedDevice = -1; + this->_connectedDevicesFuture.setFromAsync([this]() { return this->_bt.getConnectedDevices(); }); + } + } + } + } +} + +void CrossPlatformGUI::_drawASMControls() +{ + static bool ambientSoundControl = true; + static bool focusOnVoice = false; + static int asmLevel = 0; + + if (ImGui::CollapsingHeader("Ambient Sound Mode ", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("Ambient Sound Control", &ambientSoundControl); + + if (this->_headphones.isSetAsmLevelAvailable()) + { + ImGui::Text("Control ambient sound for your %ss", this->_connectedDevice.name.c_str()); + + ImGui::SliderInt("Ambient Sound Level", &asmLevel, 0, 19); + + if (this->_headphones.isFocusOnVoiceAvailable()) + { + ImGui::Checkbox("Focus on Voice", &focusOnVoice); + } + else + { + ImGui::Text("Focus on Voice isn't enabled on this level."); + } + } + + this->_headphones.setAmbientSoundControl(ambientSoundControl); + this->_headphones.setAsmLevel(asmLevel); + this->_headphones.setFocusOnVoice(focusOnVoice); + } +} + +void CrossPlatformGUI::_drawSurroundControls() +{ + static int soundPosition = 0; + static int vptType = 0; + + if (ImGui::CollapsingHeader("Virtual Sound", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Text("Only one of the options may be used at a time"); + + if (ImGui::Combo("Sound Position", &soundPosition, "Off\0Front Left\0Front Right\0" + "Front\0Rear Left\0Rear Right\0\0")) + { + vptType = 0; + } + + if (ImGui::Combo("Surround (VPT)", &vptType, "Off\0Outdoor Festival\0Arena\0" + "Concert Hall\0Club\0\0")) + { + soundPosition = 0; + } + + this->_headphones.setSurroundPosition(SOUND_POSITION_PRESET_ARRAY[soundPosition]); + this->_headphones.setVptType(vptType); + } +} + +void CrossPlatformGUI::_drawOptimizerButton() +{ + if (this->_headphones.getOptimizerState() == OPTIMIZER_STATE::IDLE) + { + if (ImGui::Button("Optimize")) + { + this->_headphones.setOptimizerState(OPTIMIZER_STATE::OPTIMIZING); + } + } + else { + // TODO: Change button to show cancel option while optimizing state + if (ImGui::Button("Optimizing... (Press To Cancel)")) + { + this->_headphones.setOptimizerState(OPTIMIZER_STATE::IDLE); + } + } +} + +void CrossPlatformGUI::_drawSpeakToChat() +{ + enum Sensitivity { Sens_Auto, Sens_High, Sens_Low, Sens_count}; + const char* Sens_hints[Sens_count] = { "Auto", "High", "Low" }; + + enum AutoOff { Time_Short, Time_Std, Time_Long, Time_Inf, Time_count}; + const char* Time_hints[Time_count] = { "Short", "Standard", "Long", "Off" }; + + static bool S2Ctoggle_check = false; + static int S2C_Sens = Sens_Auto; + static bool S2C_Voice = 0; + static int S2C_AutoOff = Time_Short; + + const char* Sens_name = (S2C_Sens >= 0 && S2C_Sens < Sens_count) ? Sens_hints[S2C_Sens] : "Unknown"; + const char* Time_name = (S2C_AutoOff >= 0 && S2C_AutoOff < Time_count) ? Time_hints[S2C_AutoOff] : "Unknown"; + + if (ImGui::CollapsingHeader("Speak To Chat Controls", ImGuiTreeNodeFlags_DefaultOpen)) + { + ImGui::Checkbox("Speak To Chat", &S2Ctoggle_check); + + if (S2Ctoggle_check) + { + if (S2Ctoggle_check){ + // ImGui::SameLine(); + ImGui::SliderInt("Speak to chat sensitivity", &S2C_Sens, 0, Sens_count - 1, Sens_name); + ImGui::SliderInt("Speak to chat Auto close", &S2C_AutoOff, 0, Time_count - 1, Time_name); + ImGui::Checkbox("Voice passthrough", &S2C_Voice); + } + else { + } + } + } + + if (S2Ctoggle_check) + this->_headphones.setS2CToggle(S2C_TOGGLE::ACTIVE); + else + this->_headphones.setS2CToggle(S2C_TOGGLE::INACTIVE); + + this->_headphones.setS2COptions(S2C_Sens, S2C_Voice, S2C_AutoOff); + +} + +void CrossPlatformGUI::_drawMultiPointConn() +{ + auto devices = this->_headphones.getDevices(); + auto connectedDevices = this->_headphones.getConnectedDevices(); + + int dev1 = connectedDevices.first; + int dev2 = connectedDevices.second; + int old_dev1 = dev1; + int old_dev2 = dev2; + + if(ImGui::CollapsingHeader("Connected Devices", ImGuiTreeNodeFlags_DefaultOpen)) + { + if (devices[dev1].name!="") + { + ImGui::Text(devices[dev1].name.c_str()); + ImGui::SameLine(); + if (ImGui::Button("Disconnect device 1")) + { + this->_headphones.setMultiPointConnection(1, 0, dev1); + } + } + + if (devices[dev1].name=="" || devices[dev2].name=="") + { + // const char* combo_preview_value = devices[dev1].name // &(devices[dev1].name); + if (ImGui::BeginCombo("Connect", "")) + { + for (int i = 0; i < devices.size(); i++) + { + if (i==dev2 || i==dev1) + continue; + + const bool is_selected = false; + if (ImGui::Selectable(devices[i].name.c_str(), is_selected)) + { + if (devices[dev1].name=="") + { + dev1 = i; + this->_headphones.setMultiPointConnection(1, dev1, 0); + } + else { + dev2 = i; + this->_headphones.setMultiPointConnection(2, dev2, 0); + } + } + + // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + ImGui::SameLine(); + if (devices[dev1].name=="") + ImGui::Text("Device 1"); + else + ImGui::Text("Device 2"); + } + + if (devices[dev2].name!="") + { + ImGui::Text(devices[dev2].name.c_str()); + ImGui::SameLine(); + if (ImGui::Button("Disconnect device 2")) + { + this->_headphones.setMultiPointConnection(2,0,dev2); + } + } + + } + +} + +void CrossPlatformGUI::_setHeadphoneSettings() { + //Don't show if the command only takes a few frames to send + static int commandLinger = 0; + + if (this->_sendCommandFuture.ready()) + { + commandLinger = 0; + try + { + this->_sendCommandFuture.get(); + } + catch (const RecoverableException& exc) + { + std::string excString; + //We kinda have to do it here and not in the wrapper, due to async causing timing issues. To fix it, the messagequeue can be made + //static, but I'm not sure if I wanna do that. + if (exc.shouldDisconnect) + { + this->_bt.disconnect(); + excString = "Disconnected due to: "; + } + this->_mq.addMessage(excString + exc.what()); + } + } + //This means that we're waiting + else if (this->_sendCommandFuture.valid()) + { + if (commandLinger++ > (FPS / 10)) + { + ImGui::Text("Sending command %c", "|/-\\"[(int)(ImGui::GetTime() / 0.05f) & 3]); + } + } + //We're not waiting, and there's no command in the air, so we can evaluate sending a new command + else if (this->_headphones.isChanged()) + { + this->_sendCommandFuture.setFromAsync([=, this]() { + return this->_headphones.setChanges(); + }); + } +} + +CrossPlatformGUI::CrossPlatformGUI(BluetoothWrapper bt) : _bt(std::move(bt)), _headphones(_bt) +{ + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + ImGuiIO& io = ImGui::GetIO(); + this->_mq = TimedMessageQueue(GUI_MAX_MESSAGES); + this->_connectedDevicesFuture.setFromAsync([this]() { return this->_bt.getConnectedDevices(); }); + + io.IniFilename = nullptr; + io.WantSaveIniSettings = false; + + //AddFontFromMemory will own the pointer, so there's no leak + char* fileData = new char[sizeof(CascadiaCodeTTF)]; + memcpy(fileData, CascadiaCodeTTF, sizeof(CascadiaCodeTTF)); + ImFont* font = io.Fonts->AddFontFromMemoryTTF(reinterpret_cast(fileData), sizeof(CascadiaCodeTTF), FONT_SIZE); + IM_ASSERT(font != NULL); +} diff --git a/Client/CrossPlatformGUI.h b/Client/CrossPlatformGUI.h index 928d3e0..fd91095 100644 --- a/Client/CrossPlatformGUI.h +++ b/Client/CrossPlatformGUI.h @@ -1,57 +1,57 @@ -#pragma once - -#include "imgui/imgui.h" -#include "Constants.h" -#include "IBluetoothConnector.h" -#include "BluetoothWrapper.h" -#include "CommandSerializer.h" -#include "Exceptions.h" -#include "TimedMessageQueue.h" -#include "SingleInstanceFuture.h" -#include "CascadiaCodeFont.h" -#include "Headphones.h" -#include "Listener.h" - -#include -#include -#include - -constexpr auto GUI_MAX_MESSAGES = 5; -constexpr auto GUI_HEIGHT = 380; -constexpr auto GUI_WIDTH = 540; -constexpr auto FPS = 60; -constexpr auto MS_PER_FRAME = 1000 / FPS; -constexpr auto FONT_SIZE = 15.0f; -const auto WINDOW_COLOR = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - -//This class should be constructed after AFTER the Dear ImGUI context is initialized. -class CrossPlatformGUI -{ -public: - CrossPlatformGUI(BluetoothWrapper bt); - - //Run the GUI code once. This function should be called from a loop from one of the GUI impls (Windows, OSX, Linux...) - //O: true if the user wants to close the window - bool performGUIPass(); -private: - void _drawErrors(); - void _drawDeviceDiscovery(); - void _drawASMControls(); - void _drawSurroundControls(); - void _setHeadphoneSettings(); - void _drawOptimizerButton(); - void _drawSpeakToChat(); - void _drawMultiPointConn(); - - BluetoothDevice _connectedDevice; - BluetoothWrapper _bt; - SingleInstanceFuture> _connectedDevicesFuture; - SingleInstanceFuture _sendCommandFuture; - SingleInstanceFuture _connectFuture; - TimedMessageQueue _mq; - Headphones _headphones; - // Listener _listener; - std::unique_ptr _listener; -}; - - +#pragma once + +#include "imgui/imgui.h" +#include "Constants.h" +#include "IBluetoothConnector.h" +#include "BluetoothWrapper.h" +#include "CommandSerializer.h" +#include "Exceptions.h" +#include "TimedMessageQueue.h" +#include "SingleInstanceFuture.h" +#include "CascadiaCodeFont.h" +#include "Headphones.h" +#include "Listener.h" + +#include +#include +#include + +constexpr auto GUI_MAX_MESSAGES = 5; +constexpr auto GUI_HEIGHT = 380; +constexpr auto GUI_WIDTH = 540; +constexpr auto FPS = 60; +constexpr auto MS_PER_FRAME = 1000 / FPS; +constexpr auto FONT_SIZE = 15.0f; +const auto WINDOW_COLOR = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + +//This class should be constructed after AFTER the Dear ImGUI context is initialized. +class CrossPlatformGUI +{ +public: + CrossPlatformGUI(BluetoothWrapper bt); + + //Run the GUI code once. This function should be called from a loop from one of the GUI impls (Windows, OSX, Linux...) + //O: true if the user wants to close the window + bool performGUIPass(); +private: + void _drawErrors(); + void _drawDeviceDiscovery(); + void _drawASMControls(); + void _drawSurroundControls(); + void _setHeadphoneSettings(); + void _drawOptimizerButton(); + void _drawSpeakToChat(); + void _drawMultiPointConn(); + + BluetoothDevice _connectedDevice; + BluetoothWrapper _bt; + SingleInstanceFuture> _connectedDevicesFuture; + SingleInstanceFuture _sendCommandFuture; + SingleInstanceFuture _connectFuture; + TimedMessageQueue _mq; + Headphones _headphones; + // Listener _listener; + std::unique_ptr _listener; +}; + + diff --git a/Client/Headphones.cpp b/Client/Headphones.cpp index c770a84..cd9f2ff 100644 --- a/Client/Headphones.cpp +++ b/Client/Headphones.cpp @@ -1,431 +1,431 @@ -#include "Headphones.h" -#include "CommandSerializer.h" - -#include - -Headphones::Headphones(BluetoothWrapper& conn) : -_conn(conn) -{ - std::vector devices({BluetoothDevice({"",""})}); - this->_savedDevices = devices; -} - -void Headphones::setAmbientSoundControl(bool val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_ambientSoundControl.desired = val; -} - -bool Headphones::getAmbientSoundControl() -{ - return this->_ambientSoundControl.current; -} - -bool Headphones::isFocusOnVoiceAvailable() -{ - return this->_ambientSoundControl.current && this->_asmLevel.current > MINIMUM_VOICE_FOCUS_STEP; -} - -void Headphones::setFocusOnVoice(bool val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_focusOnVoice.desired = val; -} - -bool Headphones::getFocusOnVoice() -{ - return this->_focusOnVoice.current; -} - -bool Headphones::isSetAsmLevelAvailable() -{ - return this->_ambientSoundControl.current; -} - -void Headphones::setAsmLevel(int val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_asmLevel.desired = val; -} - -int Headphones::getAsmLevel() -{ - return this->_asmLevel.current; -} - -void Headphones::setOptimizerState(OPTIMIZER_STATE val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_optimizerState.desired = val; -} - -OPTIMIZER_STATE Headphones::getOptimizerState() -{ - return this->_optimizerState.current; -} - -void Headphones::setS2CToggle(S2C_TOGGLE val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_speakToChat.desired = val; -} - -void Headphones::setS2COptions(int sensitivity, bool voice, int offTime) -{ - unsigned int sens = (unsigned int) sensitivity; - unsigned int time = (unsigned int) offTime; - std::lock_guard guard(this->_propertyMtx); - this->_s2cOptions.desired = { (sens << 16) | (voice << 8) | (time) }; -} - -S2C_TOGGLE Headphones::getS2CToggle() -{ - return this->_speakToChat.current; -} - -unsigned int Headphones::getS2COptions() -{ - return this->_s2cOptions.current; -} - -void Headphones::setSurroundPosition(SOUND_POSITION_PRESET val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_surroundPosition.desired = val; -} - -SOUND_POSITION_PRESET Headphones::getSurroundPosition() -{ - return this->_surroundPosition.current; -} - -void Headphones::setVptType(int val) -{ - std::lock_guard guard(this->_propertyMtx); - this->_vptType.desired = val; -} - -int Headphones::getVptType() -{ - return this->_vptType.current; -} - -bool Headphones::getMultiPointSetting() -{ - return this->_multiPointSetting; -} - -const std::vector& Headphones::getDevices() -{ - return this->_savedDevices; -} - -std::pair Headphones::getConnectedDevices() -{ - return {this->_dev1.current, this->_dev2.current}; -} - -void Headphones::setMultiPointConnection(int connectionId, int newDevice, int oldDevice) -{ - Property* dev = & this->_dev1; - - if (connectionId==1) - dev = & this->_dev1; - else - dev = & this->_dev2; - - std::lock_guard guard(this->_propertyMtx); - dev->desired = newDevice; -} - -inline void Headphones::disconnect(int deviceIdx) -{ - std::string deviceMac = this->_savedDevices[deviceIdx].mac; - this->_conn.sendCommand(CommandSerializer::serializeMultiPointCommand( - MULTI_POINT_COMMANDS::DISCONNECT, - deviceMac - ), - DATA_TYPE::DATA_MDR_NO2 - ); -} - -inline void Headphones::connect(int deviceIdx) -{ - std::string deviceMac = this->_savedDevices[deviceIdx].mac; - this->_conn.sendCommand(CommandSerializer::serializeMultiPointCommand( - MULTI_POINT_COMMANDS::CONNECT, - deviceMac - ), - DATA_TYPE::DATA_MDR_NO2 - ); -} - -bool Headphones::isChanged() -{ - return !(this->_ambientSoundControl.isFulfilled() && - this->_asmLevel.isFulfilled() && - this->_focusOnVoice.isFulfilled() && - this->_surroundPosition.isFulfilled() && - this->_vptType.isFulfilled() && - this->_optimizerState.isFulfilled() && - this->_speakToChat.isFulfilled() && - this->_s2cOptions.isFulfilled() && - this->_dev1.isFulfilled() && - this->_dev2.isFulfilled() - ); -} - -// At most one instance of this function is invoked at any time -// Synchronization not required -void Headphones::setChanges() -{ - if (!(this->_optimizerState.isFulfilled())) - { - auto state = this->_optimizerState.desired; - - this->_conn.sendCommand(CommandSerializer::serializeXM4OptimizeCommand( - state - )); - - std::lock_guard guard(this->_propertyMtx); - this->_optimizerState.fulfill(); - } - - if (!(this->_speakToChat.isFulfilled())) - { - auto s2cState = this->_speakToChat.desired; - - this->_conn.sendCommand(CommandSerializer::serializeXM4SpeakToChat( - s2cState - )); - - std::lock_guard guard(this->_propertyMtx); - this->_speakToChat.fulfill(); - } - - if (!(this->_s2cOptions.isFulfilled())) - { - auto s2cOptions = this->_s2cOptions.desired; - unsigned char sensitivity = (unsigned char) (s2cOptions >> 16) & 0xff; - unsigned char voice = (unsigned char) (s2cOptions >> 8) & 0xff; - unsigned char offTime = (unsigned char) (s2cOptions) & 0xff; - - this->_conn.sendCommand(CommandSerializer::serializeXM4_S2C_Options( - sensitivity, - voice, - offTime - )); - - std::lock_guard guard(this->_propertyMtx); - this->_s2cOptions.fulfill(); - } - - if (!(this->_ambientSoundControl.isFulfilled() && this->_focusOnVoice.isFulfilled() && this->_asmLevel.isFulfilled())) - { - auto ncAsmEffect = this->_ambientSoundControl.desired ? NC_ASM_EFFECT::ADJUSTMENT_COMPLETION : NC_ASM_EFFECT::OFF; - auto asmId = this->_focusOnVoice.desired ? ASM_ID::VOICE : ASM_ID::NORMAL; - auto asmLevel = this->_ambientSoundControl.desired ? this->_asmLevel.desired : ASM_LEVEL_DISABLED; - - this->_conn.sendCommand(CommandSerializer::serializeNcAndAsmSetting( - ncAsmEffect, - NC_ASM_SETTING_TYPE::LEVEL_ADJUSTMENT, - ASM_SETTING_TYPE::LEVEL_ADJUSTMENT, - asmId, - asmLevel - )); - - std::lock_guard guard(this->_propertyMtx); - this->_ambientSoundControl.fulfill(); - this->_asmLevel.fulfill(); - this->_focusOnVoice.fulfill(); - } - - if (!(this->_vptType.isFulfilled() && this->_surroundPosition.isFulfilled())) { - VPT_INQUIRED_TYPE command; - unsigned char preset; - - if (this->_vptType.desired != 0) { - command = VPT_INQUIRED_TYPE::VPT; - preset = static_cast(this->_vptType.desired); - } - else if (this->_surroundPosition.desired != SOUND_POSITION_PRESET::OFF) { - command = VPT_INQUIRED_TYPE::SOUND_POSITION; - preset = static_cast(this->_surroundPosition.desired); - } - else { - // Just used that one because it seems like it disables both - if (this->_surroundPosition.current != SOUND_POSITION_PRESET::OFF) { - command = VPT_INQUIRED_TYPE::SOUND_POSITION; - preset = static_cast(SOUND_POSITION_PRESET::OFF); - } - else if (this->_vptType.current != 0) { - command = VPT_INQUIRED_TYPE::VPT; - preset = 0; - } - else { - throw std::logic_error("it's impossible that both values were changed to zero and were also previously zero"); - } - } - - this->_conn.sendCommand(CommandSerializer::serializeVPTSetting(command, preset)); - - std::lock_guard guard(this->_propertyMtx); - this->_vptType.fulfill(); - this->_surroundPosition.fulfill(); - } - - if (!(this->_dev1.isFulfilled())) - { - if (this->_dev1.current!=0) - this->disconnect(this->_dev1.current); - if (this->_dev1.desired!=0) - this->connect(this->_dev1.desired); - - std::lock_guard guard(this->_propertyMtx); - this->_dev1.desired = this->_dev1.current; - } - - if (!(this->_dev2.isFulfilled())) - { - if (this->_dev2.current!=0) - this->disconnect(this->_dev2.current); - if (this->_dev2.desired!=0) - this->connect(this->_dev2.desired); - - std::lock_guard guard(this->_propertyMtx); - this->_dev2.desired = this->_dev2.current; - } - - // THIS IS A WORKAROUND - // My XM4 do not respond when 2 commands of the same function are sent back to back - // This command breaks the chain and makes it respond every time - // And I can't seem to be able to fix it - // This also queries paired devices - { - this->_conn.sendCommand({0x36,0x01}, DATA_TYPE::DATA_MDR_NO2); - } -} - -void Headphones::queryState() -{ - // Right now only one query is placed in here - // When adding more queries only the first query is ACK'd - // This is because query commands do not follow the same rule as other commands regarding sequence number - // The logic is quite weird - // TODO: fix this - this->queryMultiPointSetting(); -} - -void Headphones::setStateFromReply(BtMessage replyMessage) -{ - Buffer bytes = replyMessage.messageBytes; - COMMAND_TYPE cmd = static_cast(bytes[0]); - - switch (cmd) - { - case COMMAND_TYPE::DEVICES_QUERY_RESPONSE: - case COMMAND_TYPE::DEVICES_STATE_RESPONSE: - { - if (bytes[1] != 0x01) - // Wrong query type, break - break; - - std::vector savedDevices = std::vector({BluetoothDevice("","")}); - int dev1 = 0, dev2 = 0; - int idx = 3; - int numDevices = static_cast(bytes[2]); - for (; numDevices > 0; numDevices--) - { - std::string mac_addr = ""; - for (int i = idx; i(bytes[idx]); - - idx++; - unsigned char name_length = static_cast(bytes[idx]); - idx++; - std::string name = ""; - for (int i = idx; i_propertyMtx); - this->_savedDevices = std::move(savedDevices); - this->_dev1.setState(dev1); - this->_dev2.setState(dev2); - } - break; - } - - case COMMAND_TYPE::XM4_OPTIMIZER_RESPONSE: - { - if (bytes[2] != 0x00) - this->_optimizerState.setState(OPTIMIZER_STATE::OPTIMIZING); - else - this->_optimizerState.setState(OPTIMIZER_STATE::IDLE); - break; - } - - case COMMAND_TYPE::MULTI_POINT_SETTING_RESPONSE: - { - if (bytes[4] == 0x38) - this->_multiPointSetting = true; - else - this->_multiPointSetting = false; - - break; - } - - default: - break; - } -} - -void Headphones::queryMultiPointSetting() -{ - this->_conn.sendCommand({ - static_cast(COMMAND_TYPE::MULTI_POINT_SETTING_QUERY), - 0x01 - }); -} - -void Headphones::queryDevices() -{ - this->_conn.sendCommand({ - static_cast(COMMAND_TYPE::MULTI_POINT_DEVICES_QUERY), - 0x01 - }, - DATA_TYPE::DATA_MDR_NO2 - ); -} - -void Headphones::queryS2C() -{ - -} - -void Headphones::queryS2COptions() -{ - +#include "Headphones.h" +#include "CommandSerializer.h" + +#include + +Headphones::Headphones(BluetoothWrapper& conn) : +_conn(conn) +{ + std::vector devices({BluetoothDevice({"",""})}); + this->_savedDevices = devices; +} + +void Headphones::setAmbientSoundControl(bool val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_ambientSoundControl.desired = val; +} + +bool Headphones::getAmbientSoundControl() +{ + return this->_ambientSoundControl.current; +} + +bool Headphones::isFocusOnVoiceAvailable() +{ + return this->_ambientSoundControl.current && this->_asmLevel.current > MINIMUM_VOICE_FOCUS_STEP; +} + +void Headphones::setFocusOnVoice(bool val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_focusOnVoice.desired = val; +} + +bool Headphones::getFocusOnVoice() +{ + return this->_focusOnVoice.current; +} + +bool Headphones::isSetAsmLevelAvailable() +{ + return this->_ambientSoundControl.current; +} + +void Headphones::setAsmLevel(int val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_asmLevel.desired = val; +} + +int Headphones::getAsmLevel() +{ + return this->_asmLevel.current; +} + +void Headphones::setOptimizerState(OPTIMIZER_STATE val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_optimizerState.desired = val; +} + +OPTIMIZER_STATE Headphones::getOptimizerState() +{ + return this->_optimizerState.current; +} + +void Headphones::setS2CToggle(S2C_TOGGLE val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_speakToChat.desired = val; +} + +void Headphones::setS2COptions(int sensitivity, bool voice, int offTime) +{ + unsigned int sens = (unsigned int) sensitivity; + unsigned int time = (unsigned int) offTime; + std::lock_guard guard(this->_propertyMtx); + this->_s2cOptions.desired = { (sens << 16) | (voice << 8) | (time) }; +} + +S2C_TOGGLE Headphones::getS2CToggle() +{ + return this->_speakToChat.current; +} + +unsigned int Headphones::getS2COptions() +{ + return this->_s2cOptions.current; +} + +void Headphones::setSurroundPosition(SOUND_POSITION_PRESET val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_surroundPosition.desired = val; +} + +SOUND_POSITION_PRESET Headphones::getSurroundPosition() +{ + return this->_surroundPosition.current; +} + +void Headphones::setVptType(int val) +{ + std::lock_guard guard(this->_propertyMtx); + this->_vptType.desired = val; +} + +int Headphones::getVptType() +{ + return this->_vptType.current; +} + +bool Headphones::getMultiPointSetting() +{ + return this->_multiPointSetting; +} + +const std::vector& Headphones::getDevices() +{ + return this->_savedDevices; +} + +std::pair Headphones::getConnectedDevices() +{ + return {this->_dev1.current, this->_dev2.current}; +} + +void Headphones::setMultiPointConnection(int connectionId, int newDevice, int oldDevice) +{ + Property* dev = & this->_dev1; + + if (connectionId==1) + dev = & this->_dev1; + else + dev = & this->_dev2; + + std::lock_guard guard(this->_propertyMtx); + dev->desired = newDevice; +} + +inline void Headphones::disconnect(int deviceIdx) +{ + std::string deviceMac = this->_savedDevices[deviceIdx].mac; + this->_conn.sendCommand(CommandSerializer::serializeMultiPointCommand( + MULTI_POINT_COMMANDS::DISCONNECT, + deviceMac + ), + DATA_TYPE::DATA_MDR_NO2 + ); +} + +inline void Headphones::connect(int deviceIdx) +{ + std::string deviceMac = this->_savedDevices[deviceIdx].mac; + this->_conn.sendCommand(CommandSerializer::serializeMultiPointCommand( + MULTI_POINT_COMMANDS::CONNECT, + deviceMac + ), + DATA_TYPE::DATA_MDR_NO2 + ); +} + +bool Headphones::isChanged() +{ + return !(this->_ambientSoundControl.isFulfilled() && + this->_asmLevel.isFulfilled() && + this->_focusOnVoice.isFulfilled() && + this->_surroundPosition.isFulfilled() && + this->_vptType.isFulfilled() && + this->_optimizerState.isFulfilled() && + this->_speakToChat.isFulfilled() && + this->_s2cOptions.isFulfilled() && + this->_dev1.isFulfilled() && + this->_dev2.isFulfilled() + ); +} + +// At most one instance of this function is invoked at any time +// Synchronization not required +void Headphones::setChanges() +{ + if (!(this->_optimizerState.isFulfilled())) + { + auto state = this->_optimizerState.desired; + + this->_conn.sendCommand(CommandSerializer::serializeXM4OptimizeCommand( + state + )); + + std::lock_guard guard(this->_propertyMtx); + this->_optimizerState.fulfill(); + } + + if (!(this->_speakToChat.isFulfilled())) + { + auto s2cState = this->_speakToChat.desired; + + this->_conn.sendCommand(CommandSerializer::serializeXM4SpeakToChat( + s2cState + )); + + std::lock_guard guard(this->_propertyMtx); + this->_speakToChat.fulfill(); + } + + if (!(this->_s2cOptions.isFulfilled())) + { + auto s2cOptions = this->_s2cOptions.desired; + unsigned char sensitivity = (unsigned char) (s2cOptions >> 16) & 0xff; + unsigned char voice = (unsigned char) (s2cOptions >> 8) & 0xff; + unsigned char offTime = (unsigned char) (s2cOptions) & 0xff; + + this->_conn.sendCommand(CommandSerializer::serializeXM4_S2C_Options( + sensitivity, + voice, + offTime + )); + + std::lock_guard guard(this->_propertyMtx); + this->_s2cOptions.fulfill(); + } + + if (!(this->_ambientSoundControl.isFulfilled() && this->_focusOnVoice.isFulfilled() && this->_asmLevel.isFulfilled())) + { + auto ncAsmEffect = this->_ambientSoundControl.desired ? NC_ASM_EFFECT::ADJUSTMENT_COMPLETION : NC_ASM_EFFECT::OFF; + auto asmId = this->_focusOnVoice.desired ? ASM_ID::VOICE : ASM_ID::NORMAL; + auto asmLevel = this->_ambientSoundControl.desired ? this->_asmLevel.desired : ASM_LEVEL_DISABLED; + + this->_conn.sendCommand(CommandSerializer::serializeNcAndAsmSetting( + ncAsmEffect, + NC_ASM_SETTING_TYPE::LEVEL_ADJUSTMENT, + ASM_SETTING_TYPE::LEVEL_ADJUSTMENT, + asmId, + asmLevel + )); + + std::lock_guard guard(this->_propertyMtx); + this->_ambientSoundControl.fulfill(); + this->_asmLevel.fulfill(); + this->_focusOnVoice.fulfill(); + } + + if (!(this->_vptType.isFulfilled() && this->_surroundPosition.isFulfilled())) { + VPT_INQUIRED_TYPE command; + unsigned char preset; + + if (this->_vptType.desired != 0) { + command = VPT_INQUIRED_TYPE::VPT; + preset = static_cast(this->_vptType.desired); + } + else if (this->_surroundPosition.desired != SOUND_POSITION_PRESET::OFF) { + command = VPT_INQUIRED_TYPE::SOUND_POSITION; + preset = static_cast(this->_surroundPosition.desired); + } + else { + // Just used that one because it seems like it disables both + if (this->_surroundPosition.current != SOUND_POSITION_PRESET::OFF) { + command = VPT_INQUIRED_TYPE::SOUND_POSITION; + preset = static_cast(SOUND_POSITION_PRESET::OFF); + } + else if (this->_vptType.current != 0) { + command = VPT_INQUIRED_TYPE::VPT; + preset = 0; + } + else { + throw std::logic_error("it's impossible that both values were changed to zero and were also previously zero"); + } + } + + this->_conn.sendCommand(CommandSerializer::serializeVPTSetting(command, preset)); + + std::lock_guard guard(this->_propertyMtx); + this->_vptType.fulfill(); + this->_surroundPosition.fulfill(); + } + + if (!(this->_dev1.isFulfilled())) + { + if (this->_dev1.current!=0) + this->disconnect(this->_dev1.current); + if (this->_dev1.desired!=0) + this->connect(this->_dev1.desired); + + std::lock_guard guard(this->_propertyMtx); + this->_dev1.desired = this->_dev1.current; + } + + if (!(this->_dev2.isFulfilled())) + { + if (this->_dev2.current!=0) + this->disconnect(this->_dev2.current); + if (this->_dev2.desired!=0) + this->connect(this->_dev2.desired); + + std::lock_guard guard(this->_propertyMtx); + this->_dev2.desired = this->_dev2.current; + } + + // THIS IS A WORKAROUND + // My XM4 do not respond when 2 commands of the same function are sent back to back + // This command breaks the chain and makes it respond every time + // And I can't seem to be able to fix it + // This also queries paired devices + { + this->_conn.sendCommand({0x36,0x01}, DATA_TYPE::DATA_MDR_NO2); + } +} + +void Headphones::queryState() +{ + // Right now only one query is placed in here + // When adding more queries only the first query is ACK'd + // This is because query commands do not follow the same rule as other commands regarding sequence number + // The logic is quite weird + // TODO: fix this + this->queryMultiPointSetting(); +} + +void Headphones::setStateFromReply(BtMessage replyMessage) +{ + Buffer bytes = replyMessage.messageBytes; + COMMAND_TYPE cmd = static_cast(bytes[0]); + + switch (cmd) + { + case COMMAND_TYPE::DEVICES_QUERY_RESPONSE: + case COMMAND_TYPE::DEVICES_STATE_RESPONSE: + { + if (bytes[1] != 0x01) + // Wrong query type, break + break; + + std::vector savedDevices = std::vector({BluetoothDevice("","")}); + int dev1 = 0, dev2 = 0; + int idx = 3; + int numDevices = static_cast(bytes[2]); + for (; numDevices > 0; numDevices--) + { + std::string mac_addr = ""; + for (int i = idx; i(bytes[idx]); + + idx++; + unsigned char name_length = static_cast(bytes[idx]); + idx++; + std::string name = ""; + for (int i = idx; i_propertyMtx); + this->_savedDevices = std::move(savedDevices); + this->_dev1.setState(dev1); + this->_dev2.setState(dev2); + } + break; + } + + case COMMAND_TYPE::XM4_OPTIMIZER_RESPONSE: + { + if (bytes[2] != 0x00) + this->_optimizerState.setState(OPTIMIZER_STATE::OPTIMIZING); + else + this->_optimizerState.setState(OPTIMIZER_STATE::IDLE); + break; + } + + case COMMAND_TYPE::MULTI_POINT_SETTING_RESPONSE: + { + if (bytes[4] == 0x38) + this->_multiPointSetting = true; + else + this->_multiPointSetting = false; + + break; + } + + default: + break; + } +} + +void Headphones::queryMultiPointSetting() +{ + this->_conn.sendCommand({ + static_cast(COMMAND_TYPE::MULTI_POINT_SETTING_QUERY), + 0x01 + }); +} + +void Headphones::queryDevices() +{ + this->_conn.sendCommand({ + static_cast(COMMAND_TYPE::MULTI_POINT_DEVICES_QUERY), + 0x01 + }, + DATA_TYPE::DATA_MDR_NO2 + ); +} + +void Headphones::queryS2C() +{ + +} + +void Headphones::queryS2COptions() +{ + } \ No newline at end of file diff --git a/Client/Headphones.h b/Client/Headphones.h index e238483..2670290 100644 --- a/Client/Headphones.h +++ b/Client/Headphones.h @@ -1,107 +1,107 @@ -#pragma once - -#include "SingleInstanceFuture.h" -#include "BluetoothWrapper.h" -#include "Constants.h" - -#include -#include -#include -#include - -template -struct Property { - T current; - T desired; - - void fulfill(); - bool isFulfilled(); - void setState(T val); -}; - -class Headphones { -public: - Headphones(BluetoothWrapper& conn); - - void setAmbientSoundControl(bool val); - bool getAmbientSoundControl(); - - bool isFocusOnVoiceAvailable(); - void setFocusOnVoice(bool val); - bool getFocusOnVoice(); - - bool isSetAsmLevelAvailable(); - void setAsmLevel(int val); - int getAsmLevel(); - - void setOptimizerState(OPTIMIZER_STATE val); - OPTIMIZER_STATE getOptimizerState(); - - void setS2CToggle(S2C_TOGGLE val); - void setS2COptions(int sensitivity, bool voice, int offTime); - S2C_TOGGLE getS2CToggle(); - unsigned int getS2COptions(); - - void setSurroundPosition(SOUND_POSITION_PRESET val); - SOUND_POSITION_PRESET getSurroundPosition(); - - void setVptType(int val); - int getVptType(); - - bool getMultiPointSetting(); - const std::vector& getDevices(); - std::pair getConnectedDevices(); - void setMultiPointConnection(int connectionId, int newDevice, int oldDevice); - inline void disconnect(int deviceIdx); - inline void connect(int deviceIdx); - - bool isChanged(); - void setChanges(); - - void queryState(); - void setStateFromReply(BtMessage replyMessage); - - void queryMultiPointSetting(); - void queryDevices(); - void queryS2C(); - void queryS2COptions(); -private: - Property _ambientSoundControl = { 0 }; - Property _focusOnVoice = { 0 }; - Property _asmLevel = { 0 }; - - Property _surroundPosition = { SOUND_POSITION_PRESET::OUT_OF_RANGE, SOUND_POSITION_PRESET::OFF }; - Property _vptType = { 0 }; - - Property _optimizerState = { OPTIMIZER_STATE::IDLE }; - - Property _speakToChat = { S2C_TOGGLE::INACTIVE, S2C_TOGGLE::INACTIVE }; - Property _s2cOptions = { 0 }; - - std::vector _savedDevices; - Property _dev1 = { 0, 0 }, _dev2 = { 0, 0 }; - bool _multiPointSetting = true; - - std::mutex _propertyMtx; - - BluetoothWrapper& _conn; -}; - -template -inline void Property::fulfill() -{ - this->current = this->desired; -} - -template -inline bool Property::isFulfilled() -{ - return this->desired == this->current; -} - -template -inline void Property::setState(T val) -{ - this->current = val; - this->desired = val; +#pragma once + +#include "SingleInstanceFuture.h" +#include "BluetoothWrapper.h" +#include "Constants.h" + +#include +#include +#include +#include + +template +struct Property { + T current; + T desired; + + void fulfill(); + bool isFulfilled(); + void setState(T val); +}; + +class Headphones { +public: + Headphones(BluetoothWrapper& conn); + + void setAmbientSoundControl(bool val); + bool getAmbientSoundControl(); + + bool isFocusOnVoiceAvailable(); + void setFocusOnVoice(bool val); + bool getFocusOnVoice(); + + bool isSetAsmLevelAvailable(); + void setAsmLevel(int val); + int getAsmLevel(); + + void setOptimizerState(OPTIMIZER_STATE val); + OPTIMIZER_STATE getOptimizerState(); + + void setS2CToggle(S2C_TOGGLE val); + void setS2COptions(int sensitivity, bool voice, int offTime); + S2C_TOGGLE getS2CToggle(); + unsigned int getS2COptions(); + + void setSurroundPosition(SOUND_POSITION_PRESET val); + SOUND_POSITION_PRESET getSurroundPosition(); + + void setVptType(int val); + int getVptType(); + + bool getMultiPointSetting(); + const std::vector& getDevices(); + std::pair getConnectedDevices(); + void setMultiPointConnection(int connectionId, int newDevice, int oldDevice); + inline void disconnect(int deviceIdx); + inline void connect(int deviceIdx); + + bool isChanged(); + void setChanges(); + + void queryState(); + void setStateFromReply(BtMessage replyMessage); + + void queryMultiPointSetting(); + void queryDevices(); + void queryS2C(); + void queryS2COptions(); +private: + Property _ambientSoundControl = { 0 }; + Property _focusOnVoice = { 0 }; + Property _asmLevel = { 0 }; + + Property _surroundPosition = { SOUND_POSITION_PRESET::OUT_OF_RANGE, SOUND_POSITION_PRESET::OFF }; + Property _vptType = { 0 }; + + Property _optimizerState = { OPTIMIZER_STATE::IDLE }; + + Property _speakToChat = { S2C_TOGGLE::INACTIVE, S2C_TOGGLE::INACTIVE }; + Property _s2cOptions = { 0 }; + + std::vector _savedDevices; + Property _dev1 = { 0, 0 }, _dev2 = { 0, 0 }; + bool _multiPointSetting = true; + + std::mutex _propertyMtx; + + BluetoothWrapper& _conn; +}; + +template +inline void Property::fulfill() +{ + this->current = this->desired; +} + +template +inline bool Property::isFulfilled() +{ + return this->desired == this->current; +} + +template +inline void Property::setState(T val) +{ + this->current = val; + this->desired = val; } \ No newline at end of file diff --git a/Client/Listener.cpp b/Client/Listener.cpp index 564759e..7eb7bee 100644 --- a/Client/Listener.cpp +++ b/Client/Listener.cpp @@ -1,39 +1,39 @@ -#include "Listener.h" - -Listener::Listener(Headphones& headphones, BluetoothWrapper& bt): -_headphones(headphones), -_bt(bt) -{} - -void Listener::listen() -{ - std::cout << "Listener registered." << std::endl; - for(;;) - { - Buffer reply = this->_bt.readReplies(); - this->handle_message(reply); - } -} - -inline BtMessage Listener::parse(Buffer msg) -{ - return CommandSerializer::unpackBtMessage(msg); -} - -void Listener::handle_message(Buffer msg) -{ - BtMessage m = this->parse(msg); - this->_bt.setSeqNumber(m.seqNumber); - - if (m.dataType == DATA_TYPE::ACK) - { - this->_bt.postAck(); - this->_bt._ack.notify_all(); - } - else if (m.dataType == DATA_TYPE::DATA_MDR || m.dataType == DATA_TYPE::DATA_MDR_NO2) - { - // Set these values as current values of Headphone property - this->_headphones.setStateFromReply(m); - this->_bt.sendAck(); - } -} +#include "Listener.h" + +Listener::Listener(Headphones& headphones, BluetoothWrapper& bt): +_headphones(headphones), +_bt(bt) +{} + +void Listener::listen() +{ + std::cout << "Listener registered." << std::endl; + for(;;) + { + Buffer reply = this->_bt.readReplies(); + this->handle_message(reply); + } +} + +inline BtMessage Listener::parse(Buffer msg) +{ + return CommandSerializer::unpackBtMessage(msg); +} + +void Listener::handle_message(Buffer msg) +{ + BtMessage m = this->parse(msg); + this->_bt.setSeqNumber(m.seqNumber); + + if (m.dataType == DATA_TYPE::ACK) + { + this->_bt.postAck(); + this->_bt._ack.notify_all(); + } + else if (m.dataType == DATA_TYPE::DATA_MDR || m.dataType == DATA_TYPE::DATA_MDR_NO2) + { + // Set these values as current values of Headphone property + this->_headphones.setStateFromReply(m); + this->_bt.sendAck(); + } +} diff --git a/Client/Listener.h b/Client/Listener.h index 7bedf98..c7726eb 100644 --- a/Client/Listener.h +++ b/Client/Listener.h @@ -1,23 +1,23 @@ -#pragma once - -#include "Constants.h" -#include "BluetoothWrapper.h" -#include "CommandSerializer.h" -#include "Exceptions.h" -#include "Headphones.h" - -#include -#include - -class Listener -{ -public: - Listener(Headphones& headphones, BluetoothWrapper& bt); - void listen(); - inline BtMessage parse(Buffer msg); - void handle_message(Buffer msg); - -private: - Headphones& _headphones; - BluetoothWrapper& _bt; +#pragma once + +#include "Constants.h" +#include "BluetoothWrapper.h" +#include "CommandSerializer.h" +#include "Exceptions.h" +#include "Headphones.h" + +#include +#include + +class Listener +{ +public: + Listener(Headphones& headphones, BluetoothWrapper& bt); + void listen(); + inline BtMessage parse(Buffer msg); + void handle_message(Buffer msg); + +private: + Headphones& _headphones; + BluetoothWrapper& _bt; }; \ No newline at end of file From af233931cba2e20b2f165c627894a3d2273652ee Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Thu, 14 Sep 2023 13:34:06 +0530 Subject: [PATCH 13/15] fixed seqNumber logic and device state query --- Client/BluetoothWrapper.cpp | 6 +++--- Client/BluetoothWrapper.h | 11 +++++++++-- Client/Headphones.cpp | 17 +++-------------- Client/Listener.cpp | 4 ++-- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/Client/BluetoothWrapper.cpp b/Client/BluetoothWrapper.cpp index 21157f7..217e699 100644 --- a/Client/BluetoothWrapper.cpp +++ b/Client/BluetoothWrapper.cpp @@ -26,7 +26,7 @@ int BluetoothWrapper::sendCommand(const std::vector& bytes, DATA_TYPE dtyp { int bytesSent; std::lock_guard guard(this->_connectorMtx); - auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber ^ 0x01); + auto data = CommandSerializer::packageDataForBt(bytes, dtype, this->_seqNumber); bytesSent = this->_connector->send(data.data(), data.size()); if (dtype != DATA_TYPE::ACK) @@ -35,9 +35,9 @@ int BluetoothWrapper::sendCommand(const std::vector& bytes, DATA_TYPE dtyp return bytesSent; } -void BluetoothWrapper::sendAck() +void BluetoothWrapper::sendAck(unsigned int seqNumber) { - auto data = CommandSerializer::packageDataForBt({}, DATA_TYPE::ACK, this->_seqNumber ^ 0x01); + auto data = CommandSerializer::packageDataForBt({}, DATA_TYPE::ACK, seqNumber ^ 0x01); this->_connector->send(data.data(), data.size()); } diff --git a/Client/BluetoothWrapper.h b/Client/BluetoothWrapper.h index afa5dcd..0bf9fac 100644 --- a/Client/BluetoothWrapper.h +++ b/Client/BluetoothWrapper.h @@ -23,7 +23,7 @@ class BluetoothWrapper BluetoothWrapper& operator=(BluetoothWrapper&& other) noexcept; int sendCommand(const std::vector& bytes, DATA_TYPE dtype = DATA_TYPE::DATA_MDR); - void sendAck(); + void sendAck(unsigned int seqNumber); Buffer readReplies(); @@ -42,7 +42,14 @@ class BluetoothWrapper std::unique_ptr _connector; std::mutex _connectorMtx; std::mutex _dataMtx; - unsigned char _seqNumber = 0x01; + + /* + 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: diff --git a/Client/Headphones.cpp b/Client/Headphones.cpp index cd9f2ff..a9cde06 100644 --- a/Client/Headphones.cpp +++ b/Client/Headphones.cpp @@ -295,25 +295,14 @@ void Headphones::setChanges() std::lock_guard guard(this->_propertyMtx); this->_dev2.desired = this->_dev2.current; } - - // THIS IS A WORKAROUND - // My XM4 do not respond when 2 commands of the same function are sent back to back - // This command breaks the chain and makes it respond every time - // And I can't seem to be able to fix it - // This also queries paired devices - { - this->_conn.sendCommand({0x36,0x01}, DATA_TYPE::DATA_MDR_NO2); - } } void Headphones::queryState() { - // Right now only one query is placed in here - // When adding more queries only the first query is ACK'd - // This is because query commands do not follow the same rule as other commands regarding sequence number - // The logic is quite weird - // TODO: fix this + this->_conn.sendCommand({0x00,0x00}); // Init + this->_conn.sendCommand({0x02,0x00}); // get mac address of device this->queryMultiPointSetting(); + this->queryDevices(); } void Headphones::setStateFromReply(BtMessage replyMessage) diff --git a/Client/Listener.cpp b/Client/Listener.cpp index 7eb7bee..5d1eaa3 100644 --- a/Client/Listener.cpp +++ b/Client/Listener.cpp @@ -23,10 +23,10 @@ inline BtMessage Listener::parse(Buffer msg) void Listener::handle_message(Buffer msg) { BtMessage m = this->parse(msg); - this->_bt.setSeqNumber(m.seqNumber); if (m.dataType == DATA_TYPE::ACK) { + this->_bt.setSeqNumber(m.seqNumber); this->_bt.postAck(); this->_bt._ack.notify_all(); } @@ -34,6 +34,6 @@ void Listener::handle_message(Buffer msg) { // Set these values as current values of Headphone property this->_headphones.setStateFromReply(m); - this->_bt.sendAck(); + this->_bt.sendAck(m.seqNumber); } } From 5ef5e4e7162a6f433fe2c1b1091ecc7257117944 Mon Sep 17 00:00:00 2001 From: aybruh00 Date: Thu, 14 Sep 2023 15:39:12 +0530 Subject: [PATCH 14/15] Added device capabilities querying --- Client/Constants.h | 29 +++++++++++++++++++------- Client/CrossPlatformGUI.cpp | 20 ++++++++++-------- Client/Headphones.cpp | 41 +++++++++++++++++++++++++++++-------- Client/Headphones.h | 6 +++++- 4 files changed, 71 insertions(+), 25 deletions(-) diff --git a/Client/Constants.h b/Client/Constants.h index 6caf812..2ab3d95 100644 --- a/Client/Constants.h +++ b/Client/Constants.h @@ -95,9 +95,9 @@ enum class COMMAND_TYPE : signed char XM4_OPTIMIZER_RESPONSE = (signed char) 0x85, DEVICES_QUERY_RESPONSE = (signed char) 0x37, DEVICES_STATE_RESPONSE = (signed char) 0x39, - MULTI_POINT_SETTING_RESPONSE = (signed char) 0x07, + CAPABILITY_QUERY_RESPONSE = (signed char) 0x07, - MULTI_POINT_SETTING_QUERY = (signed char) 0x06, + CAPABILITY_QUERY = (signed char) 0x06, MULTI_POINT_DEVICES_QUERY = (signed char) 0x36, S2C_QUERY = (signed char) 0xf6, S2C_OPTIONS_QUERY = (signed char) 0xfa @@ -155,13 +155,28 @@ enum class S2C_TOGGLE : signed char INACTIVE = 0 }; -enum class QUERY_RESPONSE_TYPE : signed char +enum class MULTI_POINT_COMMANDS : signed char { - DEVICES = (signed char) 0x37, + CONNECT = (signed char) 0x01, + DISCONNECT = (signed char) 0x00, + UNPAIR = (signed char) 0x02 }; -enum class MULTI_POINT_COMMANDS : signed char +enum DEVICE_CAPABILITIES { - CONNECT = (signed char) 0x01, - DISCONNECT = (signed char) 0x00 + 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 +}; \ No newline at end of file diff --git a/Client/CrossPlatformGUI.cpp b/Client/CrossPlatformGUI.cpp index 97f6507..12775d4 100644 --- a/Client/CrossPlatformGUI.cpp +++ b/Client/CrossPlatformGUI.cpp @@ -1,4 +1,4 @@ -#include "CrossPlatformGUI.h" +#include "CrossPlatformGUI.h" bool CrossPlatformGUI::performGUIPass() { @@ -25,19 +25,21 @@ bool CrossPlatformGUI::performGUIPass() if (this->_bt.isConnected()) { + unsigned int cap = this->_headphones.getCapabilities(); + // ImGui::Spacing(); ImGui::Separator(); - this->_drawASMControls(); - if (this->_bt.isConnected() && (this->_connectedDevice.name) == "WH-1000XM4"){ + if (cap & DEVICE_CAPABILITIES::NC_ASM) + this->_drawASMControls(); + if (cap & DEVICE_CAPABILITIES::VPT) + this->_drawSurroundControls(); + if (cap & DEVICE_CAPABILITIES::SPEAK_TO_CHAT) this->_drawSpeakToChat(); - if (this->_headphones.getMultiPointSetting()) - this->_drawMultiPointConn(); + if (cap & DEVICE_CAPABILITIES::MULTI_POINT) + this->_drawMultiPointConn(); + if (cap & DEVICE_CAPABILITIES::OPTIMIZER) ImGui::Separator(); this->_drawOptimizerButton(); - } - else { - this->_drawSurroundControls(); - } this->_setHeadphoneSettings(); } diff --git a/Client/Headphones.cpp b/Client/Headphones.cpp index a9cde06..73da78c 100644 --- a/Client/Headphones.cpp +++ b/Client/Headphones.cpp @@ -297,11 +297,16 @@ void Headphones::setChanges() } } +unsigned int Headphones::getCapabilities() +{ + return this->_capabilities; +} + void Headphones::queryState() { this->_conn.sendCommand({0x00,0x00}); // Init this->_conn.sendCommand({0x02,0x00}); // get mac address of device - this->queryMultiPointSetting(); + this->queryDeviceCapabilities(); this->queryDevices(); } @@ -376,12 +381,32 @@ void Headphones::setStateFromReply(BtMessage replyMessage) break; } - case COMMAND_TYPE::MULTI_POINT_SETTING_RESPONSE: + case COMMAND_TYPE::CAPABILITY_QUERY_RESPONSE: { - if (bytes[4] == 0x38) - this->_multiPointSetting = true; - else - this->_multiPointSetting = false; + for (char c: bytes){ + switch (c) + { + case static_cast(FUNCTION_TYPE::DEVICE_MANAGEMENT): + this->_capabilities |= DEVICE_CAPABILITIES::MULTI_POINT; + break; + + case static_cast(FUNCTION_TYPE::VPT): + this->_capabilities |= DEVICE_CAPABILITIES::VPT; + break; + + case static_cast(FUNCTION_TYPE::OPTIMIZER): + this->_capabilities |= DEVICE_CAPABILITIES::OPTIMIZER; + break; + + case static_cast(FUNCTION_TYPE::NC_ASM): + this->_capabilities |= DEVICE_CAPABILITIES::NC_ASM; + break; + + case static_cast(FUNCTION_TYPE::SMART_TALKING_MODE): + this->_capabilities |= DEVICE_CAPABILITIES::SPEAK_TO_CHAT; + break; + } + } break; } @@ -391,10 +416,10 @@ void Headphones::setStateFromReply(BtMessage replyMessage) } } -void Headphones::queryMultiPointSetting() +void Headphones::queryDeviceCapabilities() { this->_conn.sendCommand({ - static_cast(COMMAND_TYPE::MULTI_POINT_SETTING_QUERY), + static_cast(COMMAND_TYPE::CAPABILITY_QUERY), 0x01 }); } diff --git a/Client/Headphones.h b/Client/Headphones.h index 2670290..345bc6f 100644 --- a/Client/Headphones.h +++ b/Client/Headphones.h @@ -61,11 +61,15 @@ class Headphones { void queryState(); void setStateFromReply(BtMessage replyMessage); - void queryMultiPointSetting(); + void queryDeviceCapabilities(); void queryDevices(); void queryS2C(); void queryS2COptions(); + + unsigned int getCapabilities(); private: + unsigned int _capabilities = 0; + Property _ambientSoundControl = { 0 }; Property _focusOnVoice = { 0 }; Property _asmLevel = { 0 }; From e330d989016bfe4075e8df1bbcd1abc8c4f8b58d Mon Sep 17 00:00:00 2001 From: Ayush Behera <70094585+aybruh00@users.noreply.github.com> Date: Sat, 23 Dec 2023 21:52:00 +0530 Subject: [PATCH 15/15] Update CommandSerializer.cpp Fixed typo --- Client/CommandSerializer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Client/CommandSerializer.cpp b/Client/CommandSerializer.cpp index d9cf7d4..fc61590 100644 --- a/Client/CommandSerializer.cpp +++ b/Client/CommandSerializer.cpp @@ -3,7 +3,7 @@ /* * Because * 0x3E represents beginning of packet - * 0xeC represents end of packet + * 0x3C represents end of packet * we need to escape these in the packet payload */ constexpr unsigned char ESCAPED_BYTE_SENTRY = 61; // 0x3D