mirror of
https://github.com/kavishdevar/librepods.git
synced 2026-02-10 19:52:24 +00:00
Merge pull request #84 from tim-gromeyer/linux-battery-class
[Linux] Add Battery Class and Fix Battery Level Assignment
This commit is contained in:
@@ -18,6 +18,7 @@ qt_add_executable(applinux
|
||||
trayiconmanager.cpp
|
||||
trayiconmanager.h
|
||||
enums.h
|
||||
battery.hpp
|
||||
)
|
||||
|
||||
qt_add_qml_module(applinux
|
||||
|
||||
@@ -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
127
linux/battery.hpp
Normal 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;
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user