From a6dbbd4f0ccc9656960e4dd363f4b95bd23a21e8 Mon Sep 17 00:00:00 2001 From: Tim Gromeyer Date: Tue, 25 Mar 2025 21:33:31 +0100 Subject: [PATCH 1/2] Simple code cleanup --- linux/airpods_packets.h | 19 +++++++++++++++++++ linux/main.cpp | 36 +++++++++++++----------------------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/linux/airpods_packets.h b/linux/airpods_packets.h index 718555c..6a4082a 100644 --- a/linux/airpods_packets.h +++ b/linux/airpods_packets.h @@ -3,6 +3,7 @@ #define AIRPODS_PACKETS_H #include +#include "enums.h" namespace AirPodsPackets { @@ -14,6 +15,24 @@ namespace AirPodsPackets static const QByteArray NOISE_CANCELLATION = HEADER + QByteArray::fromHex("02000000"); static const QByteArray TRANSPARENCY = HEADER + QByteArray::fromHex("03000000"); static const QByteArray ADAPTIVE = HEADER + QByteArray::fromHex("04000000"); + + static const QByteArray getPacketForMode(AirpodsTrayApp::Enums::NoiseControlMode mode) + { + using NoiseControlMode = AirpodsTrayApp::Enums::NoiseControlMode; + switch (mode) + { + case NoiseControlMode::Off: + return OFF; + case NoiseControlMode::NoiseCancellation: + return NOISE_CANCELLATION; + case NoiseControlMode::Transparency: + return TRANSPARENCY; + case NoiseControlMode::Adaptive: + return ADAPTIVE; + default: + return QByteArray(); + } + } } // Conversational Awareness Packets diff --git a/linux/main.cpp b/linux/main.cpp index 74ebb1c..3c02791 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -61,7 +61,7 @@ public: const QList connectedDevices = localDevice.connectedDevices(); for (const QBluetoothAddress &address : connectedDevices) { QBluetoothDeviceInfo device(address, "", 0); - if (device.serviceUuids().contains(QBluetoothUuid("74ec2172-0bad-4d01-8f77-997b2be0722a"))) { + if (isAirPodsDevice(device)) { connectToDevice(device); return; } @@ -144,6 +144,11 @@ private: QDBusConnection::systemBus().registerService("me.kavishdevar.aln"); } + bool isAirPodsDevice(const QBluetoothDeviceInfo &device) + { + return device.serviceUuids().contains(QBluetoothUuid("74ec2172-0bad-4d01-8f77-997b2be0722a")); + } + void notifyAndroidDevice() { if (phoneSocket && phoneSocket->isOpen()) @@ -178,7 +183,7 @@ private: if (connected) { const QBluetoothAddress address = QBluetoothAddress(devicePath.split("/").last().replace("_", ":")); QBluetoothDeviceInfo device(address, "", 0); - if (device.serviceUuids().contains(QBluetoothUuid("74ec2172-0bad-4d01-8f77-997b2be0722a"))) { + if (isAirPodsDevice(device)) { connectToDevice(device); } } else { @@ -219,22 +224,7 @@ public slots: void setNoiseControlMode(NoiseControlMode mode) { LOG_INFO("Setting noise control mode to: " << mode); - QByteArray packet; - switch (mode) - { - case NoiseControlMode::Off: - packet = AirPodsPackets::NoiseControl::OFF; - break; - case NoiseControlMode::NoiseCancellation: - packet = AirPodsPackets::NoiseControl::NOISE_CANCELLATION; - break; - case NoiseControlMode::Transparency: - packet = AirPodsPackets::NoiseControl::TRANSPARENCY; - break; - case NoiseControlMode::Adaptive: - packet = AirPodsPackets::NoiseControl::ADAPTIVE; - break; - } + QByteArray packet = AirPodsPackets::NoiseControl::getPacketForMode(mode); writePacketToSocket(packet, "Noise control mode packet written: "); } void setNoiseControlMode(int mode) @@ -308,7 +298,7 @@ private slots: connectToDevice(device.address().toString()); } LOG_INFO("Device discovered: " << device.name() << " (" << device.address().toString() << ")"); - if (device.serviceUuids().contains(QBluetoothUuid("74ec2172-0bad-4d01-8f77-997b2be0722a"))) { + if (isAirPodsDevice(device)) { LOG_DEBUG("Found AirPods device: " + device.name()); connectToDevice(device); } @@ -319,7 +309,7 @@ private slots: discoveryAgent->start(); const QList discoveredDevices = discoveryAgent->discoveredDevices(); for (const QBluetoothDeviceInfo &device : discoveredDevices) { - if (device.serviceUuids().contains(QBluetoothUuid("74ec2172-0bad-4d01-8f77-997b2be0722a"))) { + if (isAirPodsDevice(device)) { connectToDevice(device); return; } @@ -330,7 +320,7 @@ private slots: void onDeviceConnected(const QBluetoothAddress &address) { LOG_INFO("Device connected: " << address.toString()); QBluetoothDeviceInfo device(address, "", 0); - if (device.serviceUuids().contains(QBluetoothUuid("74ec2172-0bad-4d01-8f77-997b2be0722a"))) { + if (isAirPodsDevice(device)) { connectToDevice(device); } } @@ -597,7 +587,7 @@ private slots: QString addr = deviceProps["Address"].toString(); QBluetoothAddress btAddress(addr); QBluetoothDeviceInfo device(btAddress, "", 0); - if (device.serviceUuids().contains(QBluetoothUuid("74ec2172-0bad-4d01-8f77-997b2be0722a"))) { + if (isAirPodsDevice(device)) { connectToDevice(device); } } @@ -656,7 +646,7 @@ private slots: for (const QBluetoothAddress &address : connectedDevices) { QBluetoothDeviceInfo device(address, "", 0); LOG_DEBUG("Connected device: " << device.name() << " (" << device.address().toString() << ")"); - if (device.serviceUuids().contains(QBluetoothUuid("74ec2172-0bad-4d01-8f77-997b2be0722a"))) { + if (isAirPodsDevice(device)) { connectToDevice(device); return; } From 53960417b641dfa355f713481c5dd38502f7c89c Mon Sep 17 00:00:00 2001 From: Tim Gromeyer Date: Tue, 25 Mar 2025 22:11:43 +0100 Subject: [PATCH 2/2] Add battery class --- linux/CMakeLists.txt | 1 + linux/battery.hpp | 127 +++++++++++++++++++++++++++++++++++++++++++ linux/main.cpp | 10 +++- 3 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 linux/battery.hpp 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)