Move bt package definitions to extra file

This commit is contained in:
Tim Gromeyer
2025-03-04 21:54:56 +01:00
parent 55d06d2f65
commit adfa11c660
4 changed files with 172 additions and 66 deletions

1
.gitignore vendored
View File

@@ -657,3 +657,4 @@ obj/
!/gradle/wrapper/gradle-wrapper.jar !/gradle/wrapper/gradle-wrapper.jar
# End of https://www.toptal.com/developers/gitignore/api/qt,c++,clion,kotlin,python,android,pycharm,androidstudio,visualstudiocode,linux # End of https://www.toptal.com/developers/gitignore/api/qt,c++,clion,kotlin,python,android,pycharm,androidstudio,visualstudiocode,linux
linux/.qmlls.ini

View File

@@ -10,6 +10,8 @@ qt_standard_project_setup(REQUIRES 6.5)
qt_add_executable(applinux qt_add_executable(applinux
main.cpp main.cpp
main.h
airpods_packets.h
) )
qt_add_qml_module(applinux qt_add_qml_module(applinux

55
linux/airpods_packets.h Normal file
View File

@@ -0,0 +1,55 @@
// airpods_packets.h
#ifndef AIRPODS_PACKETS_H
#define AIRPODS_PACKETS_H
#include <QByteArray>
namespace AirPodsPackets
{
// Noise Control Mode Packets
namespace NoiseControl
{
static const QByteArray HEADER = QByteArray::fromHex("0400040009000D"); // Added for parsing
static const QByteArray OFF = HEADER + QByteArray::fromHex("01000000");
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");
}
// Conversational Awareness Packets
namespace ConversationalAwareness
{
static const QByteArray HEADER = QByteArray::fromHex("04000400090028"); // Added for parsing
static const QByteArray ENABLED = HEADER + QByteArray::fromHex("01000000");
static const QByteArray DISABLED = HEADER + QByteArray::fromHex("02000000");
static const QByteArray DATA_HEADER = QByteArray::fromHex("040004004B00020001"); // For received data
}
// Connection Packets
namespace Connection
{
static const QByteArray HANDSHAKE = QByteArray::fromHex("00000400010002000000000000000000");
static const QByteArray SET_SPECIFIC_FEATURES = QByteArray::fromHex("040004004d00ff00000000000000");
static const QByteArray REQUEST_NOTIFICATIONS = QByteArray::fromHex("040004000f00ffffffffff");
static const QByteArray AIRPODS_DISCONNECTED = QByteArray::fromHex("00010000");
}
// Phone Communication Packets
namespace Phone
{
static const QByteArray NOTIFICATION = QByteArray::fromHex("00040001");
static const QByteArray CONNECTED = QByteArray::fromHex("00010001");
static const QByteArray DISCONNECTED = QByteArray::fromHex("00010000");
static const QByteArray STATUS_REQUEST = QByteArray::fromHex("00020003");
static const QByteArray DISCONNECT_REQUEST = QByteArray::fromHex("00020000");
}
// Parsing Headers
namespace Parse
{
static const QByteArray EAR_DETECTION = QByteArray::fromHex("040004000600");
static const QByteArray BATTERY_STATUS = QByteArray::fromHex("040004000400");
}
}
#endif // AIRPODS_PACKETS_H

View File

@@ -1,4 +1,5 @@
#include "main.h" #include "main.h"
#include "airpods_packets.h"
#define LOG_INFO(msg) qCInfo(airpodsApp) << "\033[32m" << msg << "\033[0m" #define LOG_INFO(msg) qCInfo(airpodsApp) << "\033[32m" << msg << "\033[0m"
#define LOG_WARN(msg) qCWarning(airpodsApp) << "\033[33m" << msg << "\033[0m" #define LOG_WARN(msg) qCWarning(airpodsApp) << "\033[33m" << msg << "\033[0m"
@@ -187,16 +188,18 @@ private:
QDBusConnection::systemBus().registerService("me.kavishdevar.aln"); QDBusConnection::systemBus().registerService("me.kavishdevar.aln");
} }
void notifyAndroidDevice() { void notifyAndroidDevice()
if (phoneSocket && phoneSocket->isOpen()) { {
QByteArray notificationPacket = QByteArray::fromHex("00040001"); if (phoneSocket && phoneSocket->isOpen())
phoneSocket->write(notificationPacket); {
LOG_DEBUG("Sent notification packet to Android: " << notificationPacket.toHex()); phoneSocket->write(AirPodsPackets::Phone::NOTIFICATION);
} else { LOG_DEBUG("Sent notification packet to Android: " << AirPodsPackets::Phone::NOTIFICATION.toHex());
}
else
{
LOG_WARN("Phone socket is not open, cannot send notification packet"); LOG_WARN("Phone socket is not open, cannot send notification packet");
} }
} }
void onNameOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) { void onNameOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) {
if (name == "org.bluez") { if (name == "org.bluez") {
if (newOwner.isEmpty()) { if (newOwner.isEmpty()) {
@@ -257,38 +260,48 @@ public slots:
} }
} }
void setNoiseControlMode(NoiseControlMode mode) { void setNoiseControlMode(NoiseControlMode mode)
{
LOG_INFO("Setting noise control mode to: " << mode); LOG_INFO("Setting noise control mode to: " << mode);
QByteArray packet; QByteArray packet;
switch (mode) { switch (mode)
case Off: {
packet = QByteArray::fromHex("0400040009000D01000000"); case Off:
break; packet = AirPodsPackets::NoiseControl::OFF;
case NoiseCancellation: break;
packet = QByteArray::fromHex("0400040009000D02000000"); case NoiseCancellation:
break; packet = AirPodsPackets::NoiseControl::NOISE_CANCELLATION;
case Transparency: break;
packet = QByteArray::fromHex("0400040009000D03000000"); case Transparency:
break; packet = AirPodsPackets::NoiseControl::TRANSPARENCY;
case Adaptive: break;
packet = QByteArray::fromHex("0400040009000D04000000"); case Adaptive:
break; packet = AirPodsPackets::NoiseControl::ADAPTIVE;
break;
} }
if (socket && socket->isOpen()) { if (socket && socket->isOpen())
{
socket->write(packet); socket->write(packet);
LOG_DEBUG("Noise control mode packet written: " << packet.toHex()); LOG_DEBUG("Noise control mode packet written: " << packet.toHex());
} else { }
else
{
LOG_ERROR("Socket is not open, cannot write noise control mode packet"); LOG_ERROR("Socket is not open, cannot write noise control mode packet");
} }
} }
void setConversationalAwareness(bool enabled) { void setConversationalAwareness(bool enabled)
{
LOG_INFO("Setting conversational awareness to: " << (enabled ? "enabled" : "disabled")); LOG_INFO("Setting conversational awareness to: " << (enabled ? "enabled" : "disabled"));
QByteArray packet = enabled ? QByteArray::fromHex("0400040009002801000000") : QByteArray::fromHex("0400040009002802000000"); QByteArray packet = enabled ? AirPodsPackets::ConversationalAwareness::ENABLED
if (socket && socket->isOpen()) { : AirPodsPackets::ConversationalAwareness::DISABLED;
if (socket && socket->isOpen())
{
socket->write(packet); socket->write(packet);
LOG_DEBUG("Conversational awareness packet written: " << packet.toHex()); LOG_DEBUG("Conversational awareness packet written: " << packet.toHex());
} else { }
else
{
LOG_ERROR("Socket is not open, cannot write conversational awareness packet"); LOG_ERROR("Socket is not open, cannot write conversational awareness packet");
} }
} }
@@ -468,17 +481,19 @@ public slots:
} }
} }
void onDeviceDisconnected(const QBluetoothAddress &address) { void onDeviceDisconnected(const QBluetoothAddress &address)
{
LOG_INFO("Device disconnected: " << address.toString()); LOG_INFO("Device disconnected: " << address.toString());
if (socket) { if (socket)
{
LOG_WARN("Socket is still open, closing it"); LOG_WARN("Socket is still open, closing it");
socket->close(); socket->close();
socket = nullptr; socket = nullptr;
} }
if (phoneSocket && phoneSocket->isOpen()) { if (phoneSocket && phoneSocket->isOpen())
QByteArray airpodsDisconnectedPacket = QByteArray::fromHex("00010000"); {
phoneSocket->write(airpodsDisconnectedPacket); phoneSocket->write(AirPodsPackets::Connection::AIRPODS_DISCONNECTED);
LOG_DEBUG("AIRPODS_DISCONNECTED packet written: " << airpodsDisconnectedPacket.toHex()); LOG_DEBUG("AIRPODS_DISCONNECTED packet written: " << AirPodsPackets::Connection::AIRPODS_DISCONNECTED.toHex());
} }
} }
@@ -494,9 +509,9 @@ public slots:
LOG_INFO("Connected to device, sending initial packets"); LOG_INFO("Connected to device, sending initial packets");
discoveryAgent->stop(); discoveryAgent->stop();
QByteArray handshakePacket = QByteArray::fromHex("00000400010002000000000000000000"); QByteArray handshakePacket = AirPodsPackets::Connection::HANDSHAKE;
QByteArray setSpecificFeaturesPacket = QByteArray::fromHex("040004004d00ff00000000000000"); QByteArray setSpecificFeaturesPacket = AirPodsPackets::Connection::SET_SPECIFIC_FEATURES;
QByteArray requestNotificationsPacket = QByteArray::fromHex("040004000f00ffffffffff"); QByteArray requestNotificationsPacket = AirPodsPackets::Connection::REQUEST_NOTIFICATIONS;
qint64 bytesWritten = localSocket->write(handshakePacket); qint64 bytesWritten = localSocket->write(handshakePacket);
LOG_DEBUG("Handshake packet written: " << handshakePacket.toHex() << ", bytes written: " << bytesWritten); LOG_DEBUG("Handshake packet written: " << handshakePacket.toHex() << ", bytes written: " << bytesWritten);
@@ -557,10 +572,14 @@ public slots:
: "In case"; : "In case";
} }
void parseData(const QByteArray &data) { void parseData(const QByteArray &data)
{
LOG_DEBUG("Received: " << data.toHex()); LOG_DEBUG("Received: " << data.toHex());
if (data.size() == 11 && data.startsWith(QByteArray::fromHex("0400040009000D"))) {
quint8 rawMode = data[7] - 1; // Noise Control Mode
if (data.size() == 11 && data.startsWith(AirPodsPackets::NoiseControl::HEADER))
{
quint8 rawMode = data[7] - 1; // Offset still needed due to protocol
if (rawMode >= NoiseControlMode::MinValue && rawMode <= NoiseControlMode::MaxValue) if (rawMode >= NoiseControlMode::MinValue && rawMode <= NoiseControlMode::MaxValue)
{ {
NoiseControlMode mode = static_cast<NoiseControlMode>(rawMode); NoiseControlMode mode = static_cast<NoiseControlMode>(rawMode);
@@ -571,30 +590,41 @@ public slots:
{ {
LOG_ERROR("Invalid noise control mode value received: " << rawMode); LOG_ERROR("Invalid noise control mode value received: " << rawMode);
} }
} else if (data.size() == 8 && data.startsWith(QByteArray::fromHex("040004000600"))) { }
// Ear Detection
else if (data.size() == 8 && data.startsWith(AirPodsPackets::Parse::EAR_DETECTION))
{
char primary = data[6]; char primary = data[6];
char secondary = data[7]; char secondary = data[7];
QString earDetectionStatus = QString("Primary: %1, Secondary: %2") QString earDetectionStatus = QString("Primary: %1, Secondary: %2")
.arg(getEarStatus(primary), getEarStatus(secondary)); .arg(getEarStatus(primary), getEarStatus(secondary));
LOG_INFO("Ear detection status: " << earDetectionStatus); LOG_INFO("Ear detection status: " << earDetectionStatus);
emit earDetectionStatusChanged(earDetectionStatus); emit earDetectionStatusChanged(earDetectionStatus);
} else if (data.size() == 22 && data.startsWith(QByteArray::fromHex("040004000400"))) { }
// Battery Status
else if (data.size() == 22 && data.startsWith(AirPodsPackets::Parse::BATTERY_STATUS))
{
int leftLevel = data[9]; int leftLevel = data[9];
int rightLevel = data[14]; int rightLevel = data[14];
int caseLevel = data[19]; int caseLevel = data[19];
QString batteryStatus = QString("Left: %1%, Right: %2%, Case: %3%") QString batteryStatus = QString("Left: %1%, Right: %2%, Case: %3%")
.arg(leftLevel) .arg(leftLevel)
.arg(rightLevel) .arg(rightLevel)
.arg(caseLevel); .arg(caseLevel);
LOG_INFO("Battery status: " << batteryStatus); LOG_INFO("Battery status: " << batteryStatus);
emit batteryStatusChanged(batteryStatus); emit batteryStatusChanged(batteryStatus);
}
} else if (data.size() == 10 && data.startsWith(QByteArray::fromHex("040004004B00020001"))) { // Conversational Awareness Data
else if (data.size() == 10 && data.startsWith(AirPodsPackets::ConversationalAwareness::DATA_HEADER))
{
LOG_INFO("Received conversational awareness data"); LOG_INFO("Received conversational awareness data");
handleConversationalAwareness(data); handleConversationalAwareness(data);
} }
else
{
LOG_DEBUG("Unrecognized packet format: " << data.toHex());
}
} }
void handleConversationalAwareness(const QByteArray &data) { void handleConversationalAwareness(const QByteArray &data) {
LOG_DEBUG("Handling conversational awareness data: " << data.toHex()); LOG_DEBUG("Handling conversational awareness data: " << data.toHex());
static int initialVolume = -1; static int initialVolume = -1;
@@ -692,18 +722,22 @@ public slots:
phoneSocket->connectToService(phoneAddress, QBluetoothUuid("1abbb9a4-10e4-4000-a75c-8953c5471342")); phoneSocket->connectToService(phoneAddress, QBluetoothUuid("1abbb9a4-10e4-4000-a75c-8953c5471342"));
} }
void relayPacketToPhone(const QByteArray &packet) { void relayPacketToPhone(const QByteArray &packet)
if (phoneSocket && phoneSocket->isOpen()) { {
QByteArray header = QByteArray::fromHex("00040001"); if (phoneSocket && phoneSocket->isOpen())
phoneSocket->write(header + packet); {
} else { phoneSocket->write(AirPodsPackets::Phone::NOTIFICATION + packet);
}
else
{
connectToPhone(); connectToPhone();
LOG_WARN("Phone socket is not open, cannot relay packet"); LOG_WARN("Phone socket is not open, cannot relay packet");
} }
} }
void handlePhonePacket(const QByteArray &packet) { void handlePhonePacket(const QByteArray &packet) {
if (packet.startsWith(QByteArray::fromHex("00040001"))) { if (packet.startsWith(AirPodsPackets::Phone::NOTIFICATION))
{
QByteArray airpodsPacket = packet.mid(4); QByteArray airpodsPacket = packet.mid(4);
if (socket && socket->isOpen()) { if (socket && socket->isOpen()) {
socket->write(airpodsPacket); socket->write(airpodsPacket);
@@ -711,20 +745,29 @@ public slots:
} else { } else {
LOG_ERROR("Socket is not open, cannot relay packet to AirPods"); LOG_ERROR("Socket is not open, cannot relay packet to AirPods");
} }
} else if (packet.startsWith(QByteArray::fromHex("00010001"))) { }
else if (packet.startsWith(AirPodsPackets::Phone::CONNECTED))
{
LOG_INFO("AirPods connected"); LOG_INFO("AirPods connected");
isConnectedLocally = true; isConnectedLocally = true;
CrossDevice.isAvailable = false; CrossDevice.isAvailable = false;
} else if (packet.startsWith(QByteArray::fromHex("00010000"))) { }
else if (packet.startsWith(AirPodsPackets::Phone::DISCONNECTED))
{
LOG_INFO("AirPods disconnected"); LOG_INFO("AirPods disconnected");
isConnectedLocally = false; isConnectedLocally = false;
CrossDevice.isAvailable = true; CrossDevice.isAvailable = true;
} else if (packet.startsWith(QByteArray::fromHex("00020003"))) { }
else if (packet.startsWith(AirPodsPackets::Phone::STATUS_REQUEST))
{
LOG_INFO("Connection status request received"); LOG_INFO("Connection status request received");
QByteArray response = (socket && socket->isOpen()) ? QByteArray::fromHex("00010001") : QByteArray::fromHex("00010000"); QByteArray response = (socket && socket->isOpen()) ? AirPodsPackets::Phone::CONNECTED
: AirPodsPackets::Phone::DISCONNECTED;
phoneSocket->write(response); phoneSocket->write(response);
LOG_DEBUG("Sent connection status response: " << response.toHex()); LOG_DEBUG("Sent connection status response: " << response.toHex());
} else if (packet.startsWith(QByteArray::fromHex("00020000"))) { }
else if (packet.startsWith(AirPodsPackets::Phone::DISCONNECT_REQUEST))
{
LOG_INFO("Disconnect request received"); LOG_INFO("Disconnect request received");
if (socket && socket->isOpen()) { if (socket && socket->isOpen()) {
socket->close(); socket->close();
@@ -737,7 +780,9 @@ public slots:
isConnectedLocally = false; isConnectedLocally = false;
CrossDevice.isAvailable = true; CrossDevice.isAvailable = true;
} }
} else { }
else
{
if (socket && socket->isOpen()) { if (socket && socket->isOpen()) {
socket->write(packet); socket->write(packet);
LOG_DEBUG("Relayed packet to AirPods: " << packet.toHex()); LOG_DEBUG("Relayed packet to AirPods: " << packet.toHex());
@@ -787,12 +832,15 @@ public slots:
playerctlProcess->start("playerctl", QStringList() << "--follow" << "status"); playerctlProcess->start("playerctl", QStringList() << "--follow" << "status");
} }
void sendDisconnectRequestToAndroid() { void sendDisconnectRequestToAndroid()
if (phoneSocket && phoneSocket->isOpen()) { {
QByteArray disconnectRequest = QByteArray::fromHex("00020000"); if (phoneSocket && phoneSocket->isOpen())
phoneSocket->write(disconnectRequest); {
LOG_DEBUG("Sent disconnect request to Android: " << disconnectRequest.toHex()); phoneSocket->write(AirPodsPackets::Phone::DISCONNECT_REQUEST);
} else { LOG_DEBUG("Sent disconnect request to Android: " << AirPodsPackets::Phone::DISCONNECT_REQUEST.toHex());
}
else
{
LOG_WARN("Phone socket is not open, cannot send disconnect request"); LOG_WARN("Phone socket is not open, cannot send disconnect request");
} }
} }