diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 94555c7..aea0d09 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -18,6 +18,7 @@ qt_add_executable(applinux trayiconmanager.cpp trayiconmanager.h enums.h + battery.hpp ) qt_add_qml_module(applinux diff --git a/linux/battery.hpp b/linux/battery.hpp new file mode 100644 index 0000000..47e13ba --- /dev/null +++ b/linux/battery.hpp @@ -0,0 +1,127 @@ +#include +#include +#include + +#include "airpods_packets.h" + +class Battery +{ +public: + // Enum for AirPods components + enum class Component + { + Right = 0x02, + Left = 0x04, + Case = 0x08, + }; + + enum class BatteryStatus + { + Unknown = 0, + Charging = 0x01, + Discharging = 0x02, + Disconnected = 0x04, + }; + + // Struct to hold battery level and status + struct BatteryState + { + int level = 0; // Battery level (0-100), -1 if unknown + BatteryStatus status = BatteryStatus::Unknown; + }; + + // Constructor: Initialize all components to unknown state + Battery() + { + states[Component::Left] = {}; + states[Component::Right] = {}; + states[Component::Case] = {}; + } + + // Parse the battery status packet + bool parsePacket(const QByteArray &packet) + { + if (!packet.startsWith(AirPodsPackets::Parse::BATTERY_STATUS)) + { + return false; + } + + // Get battery count (number of components) + quint8 batteryCount = static_cast(packet[6]); + if (batteryCount > 3 || packet.size() != 7 + 5 * batteryCount) + { + return false; // Invalid count or size mismatch + } + + // Copy current states; only included components will be updated + QMap newStates = states; + + // Parse each component + for (quint8 i = 0; i < batteryCount; ++i) + { + int offset = 7 + (5 * i); + quint8 type = static_cast(packet[offset]); + + // Verify spacer and end bytes + if (static_cast(packet[offset + 1]) != 0x01 || + static_cast(packet[offset + 4]) != 0x01) + { + return false; + } + + // Map byte value to component + Component comp = static_cast(type); + + // Extract level and status + int level = static_cast(packet[offset + 2]); + auto status = static_cast(packet[offset + 3]); + + // Update the state for this component + newStates[comp] = {level, status}; + } + + // Apply updates; unmentioned components retain old states + states = newStates; + return true; + } + + // Get the raw state for a component + BatteryState getState(Component comp) const + { + return states.value(comp, {}); + } + + // Get a formatted status string including charging state + QString getComponentStatus(Component comp) const + { + BatteryState state = getState(comp); + if (state.level == -1) + { + return "Unknown"; + } + + QString statusStr; + switch (state.status) + { + case BatteryStatus::Unknown: + statusStr = "Unknown"; + break; + case BatteryStatus::Charging: + statusStr = "Charging"; + break; + case BatteryStatus::Discharging: + statusStr = "Discharging"; + break; + case BatteryStatus::Disconnected: + statusStr = "Disconnected"; + break; + default: + statusStr = "Invalid"; + break; + } + return QString("%1% (%2)").arg(state.level).arg(statusStr); + } + +private: + QMap states; +}; \ No newline at end of file diff --git a/linux/main.cpp b/linux/main.cpp index 3c02791..7d50774 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -6,6 +6,7 @@ #include "mediacontroller.h" #include "trayiconmanager.h" #include "enums.h" +#include "battery.hpp" using namespace AirpodsTrayApp::Enums; @@ -449,9 +450,12 @@ private slots: // Battery Status else if (data.size() == 22 && data.startsWith(AirPodsPackets::Parse::BATTERY_STATUS)) { - int leftLevel = data[9]; - int rightLevel = data[14]; - int caseLevel = data[19]; + Battery battery; + battery.parsePacket(data); + + int leftLevel = battery.getState(Battery::Component::Left).level; + int rightLevel = battery.getState(Battery::Component::Right).level; + int caseLevel = battery.getState(Battery::Component::Case).level; m_batteryStatus = QString("Left: %1%, Right: %2%, Case: %3%") .arg(leftLevel) .arg(rightLevel)