Merge pull request #84 from tim-gromeyer/linux-battery-class

[Linux] Add Battery Class and Fix Battery Level Assignment
This commit is contained in:
Kavish Devar
2025-03-26 16:17:15 +05:30
committed by GitHub
4 changed files with 167 additions and 26 deletions

View File

@@ -18,6 +18,7 @@ qt_add_executable(applinux
trayiconmanager.cpp
trayiconmanager.h
enums.h
battery.hpp
)
qt_add_qml_module(applinux

View File

@@ -3,6 +3,7 @@
#define AIRPODS_PACKETS_H
#include <QByteArray>
#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

127
linux/battery.hpp Normal file
View File

@@ -0,0 +1,127 @@
#include <QByteArray>
#include <QMap>
#include <QString>
#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<quint8>(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<Component, BatteryState> newStates = states;
// Parse each component
for (quint8 i = 0; i < batteryCount; ++i)
{
int offset = 7 + (5 * i);
quint8 type = static_cast<quint8>(packet[offset]);
// Verify spacer and end bytes
if (static_cast<quint8>(packet[offset + 1]) != 0x01 ||
static_cast<quint8>(packet[offset + 4]) != 0x01)
{
return false;
}
// Map byte value to component
Component comp = static_cast<Component>(type);
// Extract level and status
int level = static_cast<quint8>(packet[offset + 2]);
auto status = static_cast<BatteryStatus>(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<Component, BatteryState> states;
};

View File

@@ -6,6 +6,7 @@
#include "mediacontroller.h"
#include "trayiconmanager.h"
#include "enums.h"
#include "battery.hpp"
using namespace AirpodsTrayApp::Enums;
@@ -61,7 +62,7 @@ public:
const QList<QBluetoothAddress> 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 +145,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 +184,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 +225,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 +299,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 +310,7 @@ private slots:
discoveryAgent->start();
const QList<QBluetoothDeviceInfo> 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 +321,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);
}
}
@@ -459,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)
@@ -597,7 +591,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 +650,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;
}