From adfa11c660c350369fde115cd953822796467c57 Mon Sep 17 00:00:00 2001 From: Tim Gromeyer Date: Tue, 4 Mar 2025 21:54:56 +0100 Subject: [PATCH] Move bt package definitions to extra file --- .gitignore | 1 + linux/CMakeLists.txt | 2 + linux/airpods_packets.h | 55 ++++++++++++ linux/main.cpp | 180 +++++++++++++++++++++++++--------------- 4 files changed, 172 insertions(+), 66 deletions(-) create mode 100644 linux/airpods_packets.h diff --git a/.gitignore b/.gitignore index 384a986..2d8d9c4 100644 --- a/.gitignore +++ b/.gitignore @@ -657,3 +657,4 @@ obj/ !/gradle/wrapper/gradle-wrapper.jar # End of https://www.toptal.com/developers/gitignore/api/qt,c++,clion,kotlin,python,android,pycharm,androidstudio,visualstudiocode,linux +linux/.qmlls.ini diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index eb764af..41507b9 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -10,6 +10,8 @@ qt_standard_project_setup(REQUIRES 6.5) qt_add_executable(applinux main.cpp + main.h + airpods_packets.h ) qt_add_qml_module(applinux diff --git a/linux/airpods_packets.h b/linux/airpods_packets.h new file mode 100644 index 0000000..718555c --- /dev/null +++ b/linux/airpods_packets.h @@ -0,0 +1,55 @@ +// airpods_packets.h +#ifndef AIRPODS_PACKETS_H +#define AIRPODS_PACKETS_H + +#include + +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 \ No newline at end of file diff --git a/linux/main.cpp b/linux/main.cpp index 0f8022d..d027475 100644 --- a/linux/main.cpp +++ b/linux/main.cpp @@ -1,4 +1,5 @@ #include "main.h" +#include "airpods_packets.h" #define LOG_INFO(msg) qCInfo(airpodsApp) << "\033[32m" << 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"); } - void notifyAndroidDevice() { - if (phoneSocket && phoneSocket->isOpen()) { - QByteArray notificationPacket = QByteArray::fromHex("00040001"); - phoneSocket->write(notificationPacket); - LOG_DEBUG("Sent notification packet to Android: " << notificationPacket.toHex()); - } else { + void notifyAndroidDevice() + { + if (phoneSocket && phoneSocket->isOpen()) + { + phoneSocket->write(AirPodsPackets::Phone::NOTIFICATION); + LOG_DEBUG("Sent notification packet to Android: " << AirPodsPackets::Phone::NOTIFICATION.toHex()); + } + else + { LOG_WARN("Phone socket is not open, cannot send notification packet"); } } - void onNameOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) { if (name == "org.bluez") { 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); QByteArray packet; - switch (mode) { - case Off: - packet = QByteArray::fromHex("0400040009000D01000000"); - break; - case NoiseCancellation: - packet = QByteArray::fromHex("0400040009000D02000000"); - break; - case Transparency: - packet = QByteArray::fromHex("0400040009000D03000000"); - break; - case Adaptive: - packet = QByteArray::fromHex("0400040009000D04000000"); - break; + switch (mode) + { + case Off: + packet = AirPodsPackets::NoiseControl::OFF; + break; + case NoiseCancellation: + packet = AirPodsPackets::NoiseControl::NOISE_CANCELLATION; + break; + case Transparency: + packet = AirPodsPackets::NoiseControl::TRANSPARENCY; + break; + case Adaptive: + packet = AirPodsPackets::NoiseControl::ADAPTIVE; + break; } - if (socket && socket->isOpen()) { + if (socket && socket->isOpen()) + { socket->write(packet); LOG_DEBUG("Noise control mode packet written: " << packet.toHex()); - } else { + } + else + { 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")); - QByteArray packet = enabled ? QByteArray::fromHex("0400040009002801000000") : QByteArray::fromHex("0400040009002802000000"); - if (socket && socket->isOpen()) { + QByteArray packet = enabled ? AirPodsPackets::ConversationalAwareness::ENABLED + : AirPodsPackets::ConversationalAwareness::DISABLED; + if (socket && socket->isOpen()) + { socket->write(packet); LOG_DEBUG("Conversational awareness packet written: " << packet.toHex()); - } else { + } + else + { 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()); - if (socket) { + if (socket) + { LOG_WARN("Socket is still open, closing it"); socket->close(); socket = nullptr; } - if (phoneSocket && phoneSocket->isOpen()) { - QByteArray airpodsDisconnectedPacket = QByteArray::fromHex("00010000"); - phoneSocket->write(airpodsDisconnectedPacket); - LOG_DEBUG("AIRPODS_DISCONNECTED packet written: " << airpodsDisconnectedPacket.toHex()); + if (phoneSocket && phoneSocket->isOpen()) + { + phoneSocket->write(AirPodsPackets::Connection::AIRPODS_DISCONNECTED); + 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"); discoveryAgent->stop(); - QByteArray handshakePacket = QByteArray::fromHex("00000400010002000000000000000000"); - QByteArray setSpecificFeaturesPacket = QByteArray::fromHex("040004004d00ff00000000000000"); - QByteArray requestNotificationsPacket = QByteArray::fromHex("040004000f00ffffffffff"); + QByteArray handshakePacket = AirPodsPackets::Connection::HANDSHAKE; + QByteArray setSpecificFeaturesPacket = AirPodsPackets::Connection::SET_SPECIFIC_FEATURES; + QByteArray requestNotificationsPacket = AirPodsPackets::Connection::REQUEST_NOTIFICATIONS; qint64 bytesWritten = localSocket->write(handshakePacket); LOG_DEBUG("Handshake packet written: " << handshakePacket.toHex() << ", bytes written: " << bytesWritten); @@ -557,10 +572,14 @@ public slots: : "In case"; } - void parseData(const QByteArray &data) { + void parseData(const QByteArray &data) + { 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) { NoiseControlMode mode = static_cast(rawMode); @@ -571,30 +590,41 @@ public slots: { 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 secondary = data[7]; QString earDetectionStatus = QString("Primary: %1, Secondary: %2") - .arg(getEarStatus(primary), getEarStatus(secondary)); + .arg(getEarStatus(primary), getEarStatus(secondary)); LOG_INFO("Ear detection status: " << 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 rightLevel = data[14]; int caseLevel = data[19]; QString batteryStatus = QString("Left: %1%, Right: %2%, Case: %3%") - .arg(leftLevel) - .arg(rightLevel) - .arg(caseLevel); + .arg(leftLevel) + .arg(rightLevel) + .arg(caseLevel); LOG_INFO("Battery status: " << 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"); handleConversationalAwareness(data); } + else + { + LOG_DEBUG("Unrecognized packet format: " << data.toHex()); + } } - void handleConversationalAwareness(const QByteArray &data) { LOG_DEBUG("Handling conversational awareness data: " << data.toHex()); static int initialVolume = -1; @@ -692,18 +722,22 @@ public slots: phoneSocket->connectToService(phoneAddress, QBluetoothUuid("1abbb9a4-10e4-4000-a75c-8953c5471342")); } - void relayPacketToPhone(const QByteArray &packet) { - if (phoneSocket && phoneSocket->isOpen()) { - QByteArray header = QByteArray::fromHex("00040001"); - phoneSocket->write(header + packet); - } else { + void relayPacketToPhone(const QByteArray &packet) + { + if (phoneSocket && phoneSocket->isOpen()) + { + phoneSocket->write(AirPodsPackets::Phone::NOTIFICATION + packet); + } + else + { connectToPhone(); LOG_WARN("Phone socket is not open, cannot relay packet"); } } void handlePhonePacket(const QByteArray &packet) { - if (packet.startsWith(QByteArray::fromHex("00040001"))) { + if (packet.startsWith(AirPodsPackets::Phone::NOTIFICATION)) + { QByteArray airpodsPacket = packet.mid(4); if (socket && socket->isOpen()) { socket->write(airpodsPacket); @@ -711,20 +745,29 @@ public slots: } else { 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"); isConnectedLocally = true; CrossDevice.isAvailable = false; - } else if (packet.startsWith(QByteArray::fromHex("00010000"))) { + } + else if (packet.startsWith(AirPodsPackets::Phone::DISCONNECTED)) + { LOG_INFO("AirPods disconnected"); isConnectedLocally = false; CrossDevice.isAvailable = true; - } else if (packet.startsWith(QByteArray::fromHex("00020003"))) { + } + else if (packet.startsWith(AirPodsPackets::Phone::STATUS_REQUEST)) + { 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); 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"); if (socket && socket->isOpen()) { socket->close(); @@ -737,7 +780,9 @@ public slots: isConnectedLocally = false; CrossDevice.isAvailable = true; } - } else { + } + else + { if (socket && socket->isOpen()) { socket->write(packet); LOG_DEBUG("Relayed packet to AirPods: " << packet.toHex()); @@ -787,12 +832,15 @@ public slots: playerctlProcess->start("playerctl", QStringList() << "--follow" << "status"); } - void sendDisconnectRequestToAndroid() { - if (phoneSocket && phoneSocket->isOpen()) { - QByteArray disconnectRequest = QByteArray::fromHex("00020000"); - phoneSocket->write(disconnectRequest); - LOG_DEBUG("Sent disconnect request to Android: " << disconnectRequest.toHex()); - } else { + void sendDisconnectRequestToAndroid() + { + if (phoneSocket && phoneSocket->isOpen()) + { + phoneSocket->write(AirPodsPackets::Phone::DISCONNECT_REQUEST); + LOG_DEBUG("Sent disconnect request to Android: " << AirPodsPackets::Phone::DISCONNECT_REQUEST.toHex()); + } + else + { LOG_WARN("Phone socket is not open, cannot send disconnect request"); } }