diff --git a/CMakeLists.txt b/CMakeLists.txt index 033025a..578e8f3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,17 @@ add_executable(MagicPodsCore src/DeviceAnc.cpp src/aap/Aap.cpp src/aap/AapClient.cpp + src/sony/SonyBaseCmd.cpp + src/sony/SonyGetAnc.cpp + src/sony/SonySetAnc.cpp + src/sony/SonyGetCaseBattery.cpp + src/sony/SonyGetHeadphonesBattery.cpp + src/sony/SonyBaseWatcher.cpp + src/sony/SonyAncWatcher.cpp + src/sony/SonyAncHelper.cpp + src/sony/SonyClient.cpp + src/sony/SonyEnums.h + src/sony/SonyStructs.h ) add_subdirectory(dependencies) target_include_directories(MagicPodsCore PRIVATE src dependencies/json ${PROJECT_BINARY_DIR}/src) diff --git a/src/mainSonyTests.cpp b/src/mainSonyTests.cpp new file mode 100644 index 0000000..c8a4105 --- /dev/null +++ b/src/mainSonyTests.cpp @@ -0,0 +1,378 @@ +#include +#include +#include +#include "Logger.h" +#include +#include "sony/SonyBaseCmd.h" +#include "sony/SonyGetAnc.h" +#include "sony/SonyGetCaseBattery.h" +#include "sony/SonyGetHeadphonesBattery.h" +#include "sony/SonySetAnc.h" +#include "sony/SonyAncWatcher.h" +using namespace MagicPodsCore; + +void PrintBytes(std::vector bytes) +{ + #ifdef DEBUG + printf(" "); + for (int i = 0; i < bytes.size(); i++) + { + printf("%02x", bytes[i]); + } + printf("\n"); + #endif +} + +void TestRequest(SonyBaseCmd &cmd, std::vector bytesP0, std::vector bytesP1) +{ + PrintBytes(cmd.Request(0)); + if (cmd.Request(0) == bytesP0) + { + LOG_RELEASE(" [ ] Prefix 0"); + } + else + { + LOG_RELEASE(" [x] Prefix 0"); + } + + PrintBytes(cmd.Request(1)); + if (cmd.Request(1) == bytesP1) + { + LOG_RELEASE(" [ ] Prefix 1"); + } + else + { + LOG_RELEASE(" [x] Prefix 1"); + } +} + +void CompareAncStates(bool isProcessed, SonyAncState stateActual, SonyAncState stateExpected){ + if (isProcessed) + { + LOG_RELEASE(" [ ] isProcessed"); + if (stateActual.AmbientVoice == stateExpected.AmbientVoice) + { + LOG_RELEASE(" [ ] AmbientVoice"); + } + else + { + LOG_RELEASE(" [x] AmbientVoice"); + } + + if (stateActual.AncFilter == stateExpected.AncFilter) + { + LOG_RELEASE(" [ ] AncFilter"); + } + else + { + LOG_RELEASE(" [x] AncFilter"); + } + + if (stateActual.AncSwitch == stateExpected.AncSwitch) + { + LOG_RELEASE(" [ ] AncSwitch"); + } + else + { + LOG_RELEASE(" [x] AncSwitch"); + } + + if (stateActual.Volume == stateExpected.Volume) + { + LOG_RELEASE(" [ ] Volume"); + } + else + { + LOG_RELEASE(" [x] Volume"); + } + } + else + { + LOG_RELEASE(" [x] isProcessed"); + } +} + +void TestSonyGetAncResponse(std::vector bytes, SonyAncState state) +{ + SonyGetAnc cmd = SonyGetAnc(); + cmd.ProcessResponse(bytes); + CompareAncStates(cmd.IsProcessed, cmd.state, state); + +} + +void TestSonyAncWatcher(std::vector bytes, SonyAncState state) +{ + SonyAncWatcher cmd = SonyAncWatcher(); + cmd.ProcessResponse(bytes); + CompareAncStates(true, cmd.State, state); + +} + + +std::string DummyConvertDeviceBatteryType(DeviceBatteryType status) +{ + switch (status) + { + case DeviceBatteryType::Case: + return "Case"; + case DeviceBatteryType::Left: + return "Left"; + case DeviceBatteryType::Right: + return "Right"; + case DeviceBatteryType::Single: + return "Single"; + default: + return "Unknown"; + } +} + +void TestSonyGetCaseBatteryResponse(std::vector bytes, std::map Battery) +{ + SonyGetCaseBattery cmd = SonyGetCaseBattery(); + cmd.ProcessResponse(bytes); + + if (cmd.IsProcessed) + { + LOG_RELEASE(" [ ] isProcessed"); + for (auto const &b : Battery) + { + if (cmd.Battery.contains(b.first) && cmd.Battery[b.first].Battery == b.second.Battery) + { + LOG_RELEASE(" [ ] Battery %s: %d", DummyConvertDeviceBatteryType(b.first).c_str(), cmd.Battery[b.first].Battery); + } + else + { + LOG_RELEASE(" [x] Battery %s", DummyConvertDeviceBatteryType(b.first).c_str()); + } + } + } + else + { + LOG_RELEASE(" [x] isProcessed"); + } +} + +void TestSonyGetHeadphonesBatteryResponse(SonyProductIds model, std::vector bytes, std::map Battery) +{ + SonyGetHeadphonesBattery cmd = SonyGetHeadphonesBattery(model); + cmd.ProcessResponse(bytes); + + if (cmd.IsProcessed) + { + LOG_RELEASE(" [ ] isProcessed"); + for (auto const &b : Battery) + { + if (cmd.Battery.contains(b.first) && cmd.Battery[b.first].Battery == b.second.Battery) + { + LOG_RELEASE(" [ ] Battery %s: %d", DummyConvertDeviceBatteryType(b.first).c_str(), cmd.Battery[b.first].Battery); + } + else + { + LOG_RELEASE(" [x] Battery %s", DummyConvertDeviceBatteryType(b.first).c_str()); + } + } + } + else + { + LOG_RELEASE(" [x] isProcessed"); + } +} + +int Testsmain() +{ + setvbuf(stdout, NULL, _IONBF, 0); + + LOG_RELEASE("Test SonyBaseCmd request:"); + SonyBaseCmd sonyBaseCmd = SonyBaseCmd("SonyBaseCmd"); + TestRequest( + sonyBaseCmd, + std::vector{0x3e, 0x00, 0x3c}, + std::vector{0x3e, 0x00, 0x3c}); + + LOG_RELEASE("Test SonyGetAnc request:"); + SonyGetAnc sonyGetAnc = SonyGetAnc(); + TestRequest( + sonyGetAnc, + std::vector{0x3e, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x02, 0x66, 0x02, 0x76, 0x3c}, + std::vector{0x3e, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x02, 0x66, 0x02, 0x77, 0x3c}); + + SonyAncState state; + LOG_RELEASE("Test SonyGetAnc response Anc:"); + state.AncSwitch = SonyAncSwitch::On; + state.AncFilter = SonyAncFilter::Anc; + state.AmbientVoice = SonyAncFilterAmbientVoice::Off; + state.Volume = 0; + TestSonyGetAncResponse( + std::vector{0x3e, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x08, 0x67, 0x02, 0x01, 0x02, 0x02, 0x01, 0x00, 0x00, 0x83, 0x3c}, + state); + + LOG_RELEASE("Test SonyGetAnc response Ambient:"); + state.AncSwitch = SonyAncSwitch::On; + state.AncFilter = SonyAncFilter::Ambient; + state.AmbientVoice = SonyAncFilterAmbientVoice::Off; + state.Volume = 1; + TestSonyGetAncResponse( + std::vector{0x3e, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x08, 0x67, 0x02, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0x82, 0x3c}, + state); + + LOG_RELEASE("Test SonyGetAnc response Off:"); + state.AncSwitch = SonyAncSwitch::Off; + state.AncFilter = SonyAncFilter::Ambient; + state.AmbientVoice = SonyAncFilterAmbientVoice::Off; + state.Volume = 1; + TestSonyGetAncResponse( + std::vector{0x3e, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x08, 0x67, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x81, 0x3c}, + state); + + LOG_RELEASE("Test SonyGetAnc response Wind:"); + state.AncSwitch = SonyAncSwitch::On; + state.AncFilter = SonyAncFilter::Wind; + state.AmbientVoice = SonyAncFilterAmbientVoice::Off; + state.Volume = 0; + TestSonyGetAncResponse( + std::vector{0x3e, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x08, 0x67, 0x02, 0x01, 0x02, 0x01, 0x01, 0x00, 0x00, 0x83, 0x3c}, + state); + + LOG_RELEASE("Test SonyGetCaseBattery request:"); + SonyGetCaseBattery sonyGetCaseBattery = SonyGetCaseBattery(); + TestRequest( + sonyGetCaseBattery, + std::vector{0x3e, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x02, 0x20, 0x3c}, + std::vector{0x3e, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x02, 0x10, 0x02, 0x21, 0x3c}); + + LOG_RELEASE("Test SonyGetCaseBattery response:"); + + std::map BatteryCase70; + DeviceBatteryData battery; + battery.isCharging = false; + battery.Status = DeviceBatteryStatus::Cached; + battery.Battery = 70; + BatteryCase70[DeviceBatteryType::Case] = battery; + + TestSonyGetCaseBatteryResponse( + std::vector{0x3e, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x04, 0x11, 0x02, 0x46, 0x00, 0x6a, 0x3c}, + BatteryCase70); + + LOG_RELEASE("Test SonyGetHeadphonesBattery request:"); + SonyGetHeadphonesBattery sonyGetHeadphonesBattery = SonyGetHeadphonesBattery(SonyProductIds::WF_1000XM3); + TestRequest( + sonyGetHeadphonesBattery, + std::vector{0x3e, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x01, 0x1f, 0x3c}, + std::vector{0x3e, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x02, 0x10, 0x01, 0x20, 0x3c}); + + LOG_RELEASE("Test SonyGetHeadphonesBatteryL20R50response:"); + + std::map BatteryL20R50; + DeviceBatteryData batteryL; + batteryL.isCharging = false; + batteryL.Status = DeviceBatteryStatus::Connected; + batteryL.Battery = 20; + BatteryL20R50[DeviceBatteryType::Left] = batteryL; + + DeviceBatteryData batteryR; + batteryR.isCharging = false; + batteryR.Status = DeviceBatteryStatus::Connected; + batteryR.Battery = 50; + BatteryL20R50[DeviceBatteryType::Right] = batteryR; + + TestSonyGetHeadphonesBatteryResponse( + SonyProductIds::WF_1000XM3, + std::vector{0x3e, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x06, 0x11, 0x01, 0x14, 0x00, 0x32, 0x00, 0x6b, 0x3c}, + BatteryL20R50); + + LOG_RELEASE("Test SonyGetHeadphonesBatteryL80R0 response:"); + + std::map BatteryL80R0; + DeviceBatteryData batteryS; + batteryS.isCharging = false; + batteryS.Status = DeviceBatteryStatus::Connected; + batteryS.Battery = 80; + BatteryL80R0[DeviceBatteryType::Single] = batteryS; + + TestSonyGetHeadphonesBatteryResponse( + SonyProductIds::WH_1000XM4, + std::vector{0x3e, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x06, 0x11, 0x01, 0x50, 0x00, 0x00, 0x00, 0x75, 0x3c}, + BatteryL80R0); + + + LOG_RELEASE("Test SonySetAnc Anc request:"); + state.AncSwitch = SonyAncSwitch::On; + state.AncFilter = SonyAncFilter::Anc; + state.AmbientVoice = SonyAncFilterAmbientVoice::Off; + state.Volume = 0; + + SonySetAnc sonySetAncAnc = SonySetAnc(state); + TestRequest( + sonySetAncAnc, + std::vector{0x3e, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x08, 0x68, 0x02, 0x10, 0x02, 0x02, 0x01, 0x00, 0x00, 0x93, 0x3c}, + std::vector{0x3e, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x08, 0x68, 0x02, 0x10, 0x02, 0x02, 0x01, 0x00, 0x00, 0x94, 0x3c}); + + + LOG_RELEASE("Test SonySetAnc Wind request:"); + state.AncSwitch = SonyAncSwitch::On; + state.AncFilter = SonyAncFilter::Wind; + state.AmbientVoice = SonyAncFilterAmbientVoice::Off; + state.Volume = 0; + + SonySetAnc sonySetAncWind = SonySetAnc(state); + TestRequest( + sonySetAncWind, + std::vector{0x3e, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x08, 0x68, 0x02, 0x10, 0x02, 0x01, 0x01, 0x00, 0x00, 0x92, 0x3c}, + std::vector{0x3e, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x08, 0x68, 0x02, 0x10, 0x02, 0x01, 0x01, 0x00, 0x00, 0x93, 0x3c}); + + + LOG_RELEASE("Test SonySetAnc AmbientVoiceFalseVolume1 Override request:"); + state.AncSwitch = SonyAncSwitch::OnAndSave; + state.AncFilter = SonyAncFilter::Ambient; + state.AmbientVoice = SonyAncFilterAmbientVoice::Off; + state.Volume = 1; + + SonySetAnc sonySetAncAmbientFalse = SonySetAnc(state); + TestRequest( + sonySetAncAmbientFalse, + std::vector{0x3e, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x08, 0x68, 0x02, 0x11, 0x02, 0x00, 0x01, 0x00, 0x01, 0x93, 0x3c}, + std::vector{0x3e, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x08, 0x68, 0x02, 0x11, 0x02, 0x00, 0x01, 0x00, 0x01, 0x94, 0x3c}); + + + LOG_RELEASE("Test SonySetAnc Disable request:"); + state.AncSwitch = SonyAncSwitch::Off; + state.AncFilter = SonyAncFilter::Ambient; + state.AmbientVoice = SonyAncFilterAmbientVoice::Off; + state.Volume = 0; + + SonySetAnc sonySetAncDisable = SonySetAnc(state); + TestRequest( + sonySetAncDisable, + std::vector{0x3e, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x08, 0x68, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x81, 0x3c}, + std::vector{0x3e, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x08, 0x68, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x82, 0x3c}); + + + LOG_RELEASE("Test SonyAncWatcher response Anc:"); + state.AncSwitch = SonyAncSwitch::On; + state.AncFilter = SonyAncFilter::Anc; + state.AmbientVoice = SonyAncFilterAmbientVoice::Off; + state.Volume = 0; + TestSonyAncWatcher( + std::vector{0x3e, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x08, 0x69, 0x02, 0x01, 0x02, 0x02, 0x01, 0x00, 0x00, 0x85, 0x3c}, + state); + + LOG_RELEASE("Test SonyAncWatcher response Ambient:"); + state.AncSwitch = SonyAncSwitch::On; + state.AncFilter = SonyAncFilter::Ambient; + state.AmbientVoice = SonyAncFilterAmbientVoice::Off; + state.Volume = 1; + TestSonyAncWatcher( + std::vector{0x3e, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x08, 0x69, 0x02, 0x01, 0x02, 0x00, 0x01, 0x00, 0x01, 0x85, 0x3c}, + state); + + LOG_RELEASE("Test SonyAncWatcher response Off:"); + state.AncSwitch = SonyAncSwitch::Off; + state.AncFilter = SonyAncFilter::Ambient; + state.AmbientVoice = SonyAncFilterAmbientVoice::Off; + state.Volume = 1; + TestSonyAncWatcher( + std::vector{0x3e, 0x0c, 0x01, 0x00, 0x00, 0x00, 0x08, 0x69, 0x02, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x84, 0x3c}, + state); + + return 0; +} \ No newline at end of file diff --git a/src/sony/SonyAncHelper.cpp b/src/sony/SonyAncHelper.cpp new file mode 100644 index 0000000..0ee0d24 --- /dev/null +++ b/src/sony/SonyAncHelper.cpp @@ -0,0 +1,49 @@ +#include "SonyAncHelper.h" +#include "Logger.h" + +namespace MagicPodsCore +{ +std::string SonyAncHelper::DummyConvertSonyAncSwitch(SonyAncSwitch status) + { + switch (status) + { + case SonyAncSwitch::Off: + return "Off"; + case SonyAncSwitch::On: + return "On"; + case SonyAncSwitch::OnAndSave: + return "OnAndSave"; + default: + return "Unknown"; + } + } + + std::string SonyAncHelper::DummyConvertSonyAncFilter(SonyAncFilter status) + { + switch (status) + { + case SonyAncFilter::Ambient: + return "Ambient"; + + case SonyAncFilter::Anc: + return "Anc"; + case SonyAncFilter::Wind: + return "Wind"; + default: + return "Unknown"; + } + } + + std::string SonyAncHelper::DummyConvertSonyAncFilterAmbientVoice(SonyAncFilterAmbientVoice status) + { + switch (status) + { + case SonyAncFilterAmbientVoice::Off: + return "Off"; + case SonyAncFilterAmbientVoice::On: + return "On"; + default: + return "Unknown"; + } + } +} \ No newline at end of file diff --git a/src/sony/SonyAncHelper.h b/src/sony/SonyAncHelper.h new file mode 100644 index 0000000..8602230 --- /dev/null +++ b/src/sony/SonyAncHelper.h @@ -0,0 +1,14 @@ +#pragma once +#include +#include "SonyEnums.h" + +namespace MagicPodsCore +{ + class SonyAncHelper + { + public: + static std::string DummyConvertSonyAncSwitch(SonyAncSwitch status); + static std::string DummyConvertSonyAncFilter(SonyAncFilter status); + static std::string DummyConvertSonyAncFilterAmbientVoice(SonyAncFilterAmbientVoice status); + }; +} \ No newline at end of file diff --git a/src/sony/SonyAncWatcher.cpp b/src/sony/SonyAncWatcher.cpp new file mode 100644 index 0000000..bacc4b4 --- /dev/null +++ b/src/sony/SonyAncWatcher.cpp @@ -0,0 +1,29 @@ +#include "SonyAncWatcher.h" +#include "SonyAncHelper.h" +#include "Logger.h" + +namespace MagicPodsCore +{ + SonyAncWatcher::SonyAncWatcher() : SonyBaseWatcher{"SonyAncWatcher"} + { + } + + void SonyAncWatcher::ProcessResponse(const std::vector &bytes) + { + if (bytes.size() == 17 && bytes[6] == 8 && bytes[7] == 105) + { + State.AncSwitch = bytes[9] == 1 ? SonyAncSwitch::On : SonyAncSwitch::Off; + State.AncFilter = static_cast(bytes[11]); + State.AmbientVoice = static_cast(bytes[13]); + State.Volume = bytes[14]; + + LOG_RELEASE("%s %s %s Volume: %d", + SonyAncHelper::DummyConvertSonyAncSwitch(State.AncSwitch).c_str(), + SonyAncHelper::DummyConvertSonyAncFilter(State.AncFilter).c_str(), + SonyAncHelper::DummyConvertSonyAncFilterAmbientVoice(State.AmbientVoice).c_str(), + State.Volume); + + _event.FireEvent(State); + } + } +} \ No newline at end of file diff --git a/src/sony/SonyAncWatcher.h b/src/sony/SonyAncWatcher.h new file mode 100644 index 0000000..8c93d4c --- /dev/null +++ b/src/sony/SonyAncWatcher.h @@ -0,0 +1,22 @@ +#pragma once +#include "SonyBaseWatcher.h" +#include "SonyGetAnc.h" +#include "../DeviceBattery.h" + +namespace MagicPodsCore +{ + class SonyAncWatcher : public SonyBaseWatcher + { + public: + SonyAncState State{}; + SonyAncWatcher(); + void ProcessResponse(const std::vector &bytes) override; + Event &GetEvent() + { + return _event; + } + + private: + Event _event{}; + }; +} diff --git a/src/sony/SonyBaseCmd.cpp b/src/sony/SonyBaseCmd.cpp new file mode 100644 index 0000000..fd4367b --- /dev/null +++ b/src/sony/SonyBaseCmd.cpp @@ -0,0 +1,47 @@ + +#include "SonyBaseCmd.h" +#include "Logger.h" + +namespace MagicPodsCore +{ + SonyBaseCmd::SonyBaseCmd(std::string tag) : _tag{tag} + { + } + void SonyBaseCmd::PrintAsHex() + { + LOG_DEBUG("%s ", _tag.c_str()); + std::vector bytes = Request(1); + for (int i = 0; i < bytes.size(); i++) + printf("%02x", bytes[i]); + } + + std::vector SonyBaseCmd::Request(unsigned char prefix) + { + return CreateCompletePacket(CreatePacketBody(prefix)); + } + + std::vector SonyBaseCmd::CreatePacketBody(unsigned char prefix) const + { + return std::vector(); + } + + std::vector SonyBaseCmd::CreateCompletePacket(std::vector packetBody) + { + unsigned char crc = 0; + + for (int i = 0; i < packetBody.size(); i++) + crc += packetBody[i]; + + packetBody.insert(packetBody.begin(), startByte); + packetBody.insert(packetBody.end(), crc); + packetBody.insert(packetBody.end(), endByte); + + return packetBody; + } + + void SonyBaseCmd::ProcessResponse(const std::vector &bytes) + { + IsProcessed = true; + LOG_DEBUG("Request %s processed", _tag.c_str()); + } +} \ No newline at end of file diff --git a/src/sony/SonyBaseCmd.h b/src/sony/SonyBaseCmd.h new file mode 100644 index 0000000..61ce876 --- /dev/null +++ b/src/sony/SonyBaseCmd.h @@ -0,0 +1,35 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace MagicPodsCore +{ + class SonyBaseCmd + { + protected: + std::string _tag{}; + + public: + bool IsProcessed; + explicit SonyBaseCmd(std::string tag); + std::vector Request(unsigned char prefix); + void PrintAsHex(); + virtual void ProcessResponse(const std::vector &bytes); + + protected: + // First byte of packet for battery, ANC commands + unsigned char startByte = 62; + + // The last byte of all packet. I do not know why + unsigned char endByte = 60; + + // Create packet body without beginning and end. This body used to calculate CRC. + virtual std::vector CreatePacketBody(unsigned char prefix) const; + + // Add start and end bytes and calculate CRC to CreatePacketBody + std::vector CreateCompletePacket(std::vector packetBody); + }; +} \ No newline at end of file diff --git a/src/sony/SonyBaseWatcher.cpp b/src/sony/SonyBaseWatcher.cpp new file mode 100644 index 0000000..5deea74 --- /dev/null +++ b/src/sony/SonyBaseWatcher.cpp @@ -0,0 +1,9 @@ +#include "SonyBaseWatcher.h" +#include "Logger.h" + +namespace MagicPodsCore +{ + SonyBaseWatcher::SonyBaseWatcher(std::string tag) : _tag{tag} + { + } +} \ No newline at end of file diff --git a/src/sony/SonyBaseWatcher.h b/src/sony/SonyBaseWatcher.h new file mode 100644 index 0000000..fcb06bc --- /dev/null +++ b/src/sony/SonyBaseWatcher.h @@ -0,0 +1,19 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace MagicPodsCore +{ + class SonyBaseWatcher + { + protected: + std::string _tag{}; + + public: + explicit SonyBaseWatcher(std::string tag); + virtual void ProcessResponse(const std::vector &bytes) = 0; + }; +} \ No newline at end of file diff --git a/src/sony/SonyClient.cpp b/src/sony/SonyClient.cpp new file mode 100644 index 0000000..f0c9c98 --- /dev/null +++ b/src/sony/SonyClient.cpp @@ -0,0 +1,65 @@ + +#include "SonyClient.h" +#include "Logger.h" +#include +#include // UNIX ONLY + + +namespace MagicPodsCore +{ + SonyClient::SonyClient(std::string tag) : _tag{tag} + { + } + + void SonyClient::WaitResult(SonyBaseCmd &cmd, int timeOutSeconds) + { + //TODO: Check connection. If connection is disconnected we unable to send cmd + + //TODO: Subscribe to bytes from headphones cmd.ProcessResponse += + + unsigned char prefix = Prefix; + + for (size_t i = 0; i <= 1; i++) + { + if (i == 1){ + prefix = prefix == 0? (unsigned char)1 : (unsigned char)0; + } + + auto requestBytes = cmd.Request(prefix); + //TODO: Send requestBytes to headphones + + auto timeout = std::chrono::system_clock::now() + std::chrono::seconds(timeOutSeconds); + while (std::chrono::system_clock::now() < timeout) + { + usleep(10000); //10 ms UNIX only + + if (cmd.IsProcessed) + break; + } + + if (cmd.IsProcessed) + break; + } + //TODO: Unsubscribe to bytes from headphones cmd.ProcessResponse -= + } + + void SonyClient::ProcessResponse(const std::vector &bytes) + { + if (bytes.size() >= 2 && bytes.size() != 9 && bytes[1] != 1) + { + + // Next command should use inverse prefix. + // If headphones send command with prefix 0, + // command from our application must set prefix 1 and vice versa + auto response = bytes[2] == 0 ? std::vector{0x3e, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x02, 0x3c} : std::vector{0x3e, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x3c}; + Prefix = bytes[2] == 0 ? (unsigned char)1 : (unsigned char)0; + LOG_DEBUG("%s New prefix %d", _tag.c_str(), Prefix); + + // Notifies the headphones that the command has been successfully received + // Send verification response like 3e010000000000013c or 3e010100000000023c + // TODO: Send response to Sony headphones + } + + // TODO: Event bytes received from Sony headphones + } +} \ No newline at end of file diff --git a/src/sony/SonyClient.h b/src/sony/SonyClient.h new file mode 100644 index 0000000..d5f4f06 --- /dev/null +++ b/src/sony/SonyClient.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include +#include +#include +#include +#include "SonyBaseCmd.h" + +namespace MagicPodsCore +{ + //CLASS NOT TESTED + class SonyClient + { + protected: + std::string _tag{}; + + public: + unsigned char Prefix; + explicit SonyClient(std::string tag); + void WaitResult(SonyBaseCmd &cmd, int timeOutSeconds = 2); + private: + void ProcessResponse(const std::vector &bytes); + }; +} \ No newline at end of file diff --git a/src/sony/SonyEnums.h b/src/sony/SonyEnums.h new file mode 100644 index 0000000..beb2a4b --- /dev/null +++ b/src/sony/SonyEnums.h @@ -0,0 +1,32 @@ +#pragma once + +namespace MagicPodsCore +{ + + enum class SonyAncFilter : unsigned char + { + Ambient = 0x00, + Wind = 0x01, + Anc = 0x02, + }; + + enum class SonyAncFilterAmbientVoice : unsigned char + { + Off = 0x00, + On = 0x01, + }; + + enum class SonyAncSwitch : unsigned char + { + Off = 0x00, + On = 0x10, + //Save current mode to headphones + //When user click switch mode button on headphones, the headphones will switch next mode based on saved setting. + //Default cycle: ANC -> Ambient -> Disable. + //If you Ambient, the next mode will be "Disable" when user click headphones button. + //if you Ambient, the next mode will be any, since we are not changing the default cycle. + //Option also write Volume and Voice settings for ambient mode. + //Option can override Ambient mode to Wind mode, the headphones will speak "Ambient mode", but the real mode will be wind. This is strange, but works. + OnAndSave = 0x11, + }; +} diff --git a/src/sony/SonyGetAnc.cpp b/src/sony/SonyGetAnc.cpp new file mode 100644 index 0000000..a45e3aa --- /dev/null +++ b/src/sony/SonyGetAnc.cpp @@ -0,0 +1,34 @@ + +#include "SonyGetAnc.h" +#include "SonyAncHelper.h" +#include "Logger.h" + +namespace MagicPodsCore +{ + SonyGetAnc::SonyGetAnc() : SonyBaseCmd{"SonyGetAnc"} + { + } + + std::vector SonyGetAnc::CreatePacketBody(unsigned char prefix) const + { + return std::vector{0x0c, prefix, 0x00, 0x00, 0x00, 0x02, 0x66, 0x02}; + } + + void SonyGetAnc::ProcessResponse(const std::vector &bytes) + { + if (bytes.size() == 17 && bytes[6] == 8 && bytes[7] == 103) + { + state.AncSwitch = bytes[9] == 1 ? SonyAncSwitch::On : SonyAncSwitch::Off; + state.AncFilter = static_cast(bytes[11]); + state.AmbientVoice = static_cast(bytes[13]); + state.Volume = bytes[14]; + + LOG_RELEASE("%s %s %s Volume: %d", + SonyAncHelper::DummyConvertSonyAncSwitch(state.AncSwitch).c_str(), + SonyAncHelper::DummyConvertSonyAncFilter(state.AncFilter).c_str(), + SonyAncHelper::DummyConvertSonyAncFilterAmbientVoice(state.AmbientVoice).c_str(), + state.Volume); + SonyBaseCmd::ProcessResponse(bytes); + } + } +} \ No newline at end of file diff --git a/src/sony/SonyGetAnc.h b/src/sony/SonyGetAnc.h new file mode 100644 index 0000000..cb35a48 --- /dev/null +++ b/src/sony/SonyGetAnc.h @@ -0,0 +1,37 @@ +#pragma once +#include "SonyBaseCmd.h" +#include "SonyStructs.h" + +namespace MagicPodsCore +{ + // --------------------------------------------------------------------------- + // Request + // 3e0c00000000026602763c + // |Body packet | + // 0 1 2 3 4 5 6 7 8 9 10 + // s p c cr e + // 3e 0c 00 00 00 00 02 66 02 76 3c + // + // 0 byte: + // 3e - Packet start (const) + // + // 2 byte: + // 00 - prefix + // 01 - prefix + // + // 7 byte: command + // + // 9 byte: CRC + // + // 10 byte: + // 3c - Packed end (const) + // --------------------------------------------------------------------------- + class SonyGetAnc : public SonyBaseCmd { + public: + SonyGetAnc(); + SonyAncState state{}; + void ProcessResponse(const std::vector &bytes) override; + protected: + std::vectorCreatePacketBody(unsigned char prefix) const override; + }; +} diff --git a/src/sony/SonyGetCaseBattery.cpp b/src/sony/SonyGetCaseBattery.cpp new file mode 100644 index 0000000..dd4734f --- /dev/null +++ b/src/sony/SonyGetCaseBattery.cpp @@ -0,0 +1,34 @@ + +#include "SonyGetCaseBattery.h" +#include "Logger.h" + + +namespace MagicPodsCore +{ + SonyGetCaseBattery::SonyGetCaseBattery() : SonyBaseCmd{"SonyGetCaseBattery"} + { + } + + std::vector SonyGetCaseBattery::CreatePacketBody(unsigned char prefix) const + { + return std::vector{0x0c, prefix, 0x00, 0x00, 0x00, 0x02, 0x10, 0x02}; + } + + void SonyGetCaseBattery::ProcessResponse(const std::vector &bytes) + { + if (bytes.size() == 13 && bytes[6] == 4 && bytes[7] == 17) + { + auto c = bytes[9]; + + DeviceBatteryData battery; + battery.isCharging = false; + battery.Status = DeviceBatteryStatus::Cached; + battery.Battery = bytes[9]; + + Battery[DeviceBatteryType::Case] = battery; + // Event battery changed + LOG_DEBUG("Battery case: %d", battery.Battery); + SonyBaseCmd::ProcessResponse(bytes); + } + } +} \ No newline at end of file diff --git a/src/sony/SonyGetCaseBattery.h b/src/sony/SonyGetCaseBattery.h new file mode 100644 index 0000000..54d60bc --- /dev/null +++ b/src/sony/SonyGetCaseBattery.h @@ -0,0 +1,15 @@ +#pragma once +#include "SonyBaseCmd.h" +#include "../DeviceBattery.h" + +namespace MagicPodsCore +{ + class SonyGetCaseBattery : public SonyBaseCmd { + public: + std::map Battery; + SonyGetCaseBattery(); + void ProcessResponse(const std::vector &bytes) override; + protected: + std::vectorCreatePacketBody(unsigned char prefix) const override; + }; +} diff --git a/src/sony/SonyGetHeadphonesBattery.cpp b/src/sony/SonyGetHeadphonesBattery.cpp new file mode 100644 index 0000000..d44cede --- /dev/null +++ b/src/sony/SonyGetHeadphonesBattery.cpp @@ -0,0 +1,53 @@ + +#include "SonyGetHeadphonesBattery.h" +#include "Logger.h" + +namespace MagicPodsCore +{ + SonyGetHeadphonesBattery::SonyGetHeadphonesBattery(SonyProductIds model) : SonyBaseCmd{"SonyGetHeadphonesBattery"} + { + SonyGetHeadphonesBattery::model = model; + } + + std::vector SonyGetHeadphonesBattery::CreatePacketBody(unsigned char prefix) const + { + return std::vector{0x0c, prefix, 0x00, 0x00, 0x00, 0x02, 0x10, 0x01}; + } + + void SonyGetHeadphonesBattery::ProcessResponse(const std::vector &bytes) + { + if (bytes.size() == 15 && bytes[6] == 6 && bytes[7] == 17) + { + auto l = bytes[9]; + auto r = bytes[11]; + + if (model == SonyProductIds::WH_1000XM4){ + DeviceBatteryData batteryS; + batteryS.isCharging = false; + batteryS.Status = l == 0 ? DeviceBatteryStatus::Disconnected : DeviceBatteryStatus::Connected; + batteryS.Battery = l; + + Battery[DeviceBatteryType::Single] = batteryS; + } + else{ + DeviceBatteryData batteryL; + batteryL.isCharging = false; + batteryL.Status = l == 0 ? DeviceBatteryStatus::Disconnected : DeviceBatteryStatus::Connected; + batteryL.Battery = l; + + DeviceBatteryData batteryR; + batteryR.isCharging = false; + batteryR.Status = r == 0 ? DeviceBatteryStatus::Disconnected : DeviceBatteryStatus::Connected; + batteryR.Battery = r; + + Battery[DeviceBatteryType::Left] = batteryL; + Battery[DeviceBatteryType::Right] = batteryR; + + } + // Event battery changed + LOG_DEBUG("Battery Left: %d Right %d", l, r); + + SonyBaseCmd::ProcessResponse(bytes); + } + } +} \ No newline at end of file diff --git a/src/sony/SonyGetHeadphonesBattery.h b/src/sony/SonyGetHeadphonesBattery.h new file mode 100644 index 0000000..4bd276e --- /dev/null +++ b/src/sony/SonyGetHeadphonesBattery.h @@ -0,0 +1,18 @@ +#pragma once +#include "SonyBaseCmd.h" +#include "SonyProductIds.h" +#include "../DeviceBattery.h" + +namespace MagicPodsCore +{ + class SonyGetHeadphonesBattery : public SonyBaseCmd { + public: + std::map Battery; + SonyGetHeadphonesBattery(SonyProductIds model); + void ProcessResponse(const std::vector &bytes) override; + private: + SonyProductIds model; + protected: + std::vectorCreatePacketBody(unsigned char prefix) const override; + }; +} diff --git a/src/sony/SonyProductIds.h b/src/sony/SonyProductIds.h new file mode 100644 index 0000000..ceb6e9d --- /dev/null +++ b/src/sony/SonyProductIds.h @@ -0,0 +1,12 @@ +#pragma once + +// Modalias: bluetooth:v{VendorIds}p{AppleProductIds}dB087 +enum class SonyProductIds : unsigned short { + WH_1000XM4 = 0x0d58, + WF_1000XM3 = 0x0ce0, +}; + +static const SonyProductIds AllSonyProductIds[] = { + SonyProductIds::WH_1000XM4, + SonyProductIds::WF_1000XM3, +}; \ No newline at end of file diff --git a/src/sony/SonySetAnc.cpp b/src/sony/SonySetAnc.cpp new file mode 100644 index 0000000..e5c8dac --- /dev/null +++ b/src/sony/SonySetAnc.cpp @@ -0,0 +1,24 @@ + +#include "SonySetAnc.h" +#include "Logger.h" + +namespace MagicPodsCore +{ + SonySetAnc::SonySetAnc(SonyAncState state) : SonyBaseCmd{"SonySetAnc"} + { + SonySetAnc::state = state; + } + + std::vector SonySetAnc::CreatePacketBody(unsigned char prefix) const + { + return std::vector{0x0c, prefix, 0x00, 0x00, 0x00, 0x08, 0x68, 0x02, (unsigned char)state.AncSwitch, 0x02, (unsigned char)state.AncFilter, 0x01, (unsigned char)state.AmbientVoice, state.Volume }; + } + + void SonySetAnc::ProcessResponse(const std::vector &bytes) + { + if (bytes.size() == 9 && bytes[1] == 1) + { + SonyBaseCmd::ProcessResponse(bytes); + } + } +} \ No newline at end of file diff --git a/src/sony/SonySetAnc.h b/src/sony/SonySetAnc.h new file mode 100644 index 0000000..1ba4c1e --- /dev/null +++ b/src/sony/SonySetAnc.h @@ -0,0 +1,15 @@ +#pragma once +#include "SonyBaseCmd.h" +#include "SonyStructs.h" + +namespace MagicPodsCore +{ + class SonySetAnc : public SonyBaseCmd { + public: + SonySetAnc(SonyAncState state); + SonyAncState state{}; + void ProcessResponse(const std::vector &bytes) override; + protected: + std::vectorCreatePacketBody(unsigned char prefix) const override; + }; +} diff --git a/src/sony/SonyStructs.h b/src/sony/SonyStructs.h new file mode 100644 index 0000000..3065949 --- /dev/null +++ b/src/sony/SonyStructs.h @@ -0,0 +1,12 @@ +#pragma once +#include "SonyEnums.h" + +namespace MagicPodsCore +{ + struct SonyAncState { + SonyAncSwitch AncSwitch{}; + SonyAncFilter AncFilter{}; + SonyAncFilterAmbientVoice AmbientVoice{}; + unsigned char Volume{}; + }; +}