Skip to content

Commit 1583f35

Browse files
authored
[Linux] Move bt package definitions to extra file
Merge pull request #77 from tim-gromeyer/packet-definitions
2 parents 55d06d2 + adfa11c commit 1583f35

File tree

4 files changed

+172
-66
lines changed

4 files changed

+172
-66
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,3 +657,4 @@ obj/
657657
!/gradle/wrapper/gradle-wrapper.jar
658658

659659
# End of https://www.toptal.com/developers/gitignore/api/qt,c++,clion,kotlin,python,android,pycharm,androidstudio,visualstudiocode,linux
660+
linux/.qmlls.ini

linux/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ qt_standard_project_setup(REQUIRES 6.5)
1010

1111
qt_add_executable(applinux
1212
main.cpp
13+
main.h
14+
airpods_packets.h
1315
)
1416

1517
qt_add_qml_module(applinux

linux/airpods_packets.h

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// airpods_packets.h
2+
#ifndef AIRPODS_PACKETS_H
3+
#define AIRPODS_PACKETS_H
4+
5+
#include <QByteArray>
6+
7+
namespace AirPodsPackets
8+
{
9+
// Noise Control Mode Packets
10+
namespace NoiseControl
11+
{
12+
static const QByteArray HEADER = QByteArray::fromHex("0400040009000D"); // Added for parsing
13+
static const QByteArray OFF = HEADER + QByteArray::fromHex("01000000");
14+
static const QByteArray NOISE_CANCELLATION = HEADER + QByteArray::fromHex("02000000");
15+
static const QByteArray TRANSPARENCY = HEADER + QByteArray::fromHex("03000000");
16+
static const QByteArray ADAPTIVE = HEADER + QByteArray::fromHex("04000000");
17+
}
18+
19+
// Conversational Awareness Packets
20+
namespace ConversationalAwareness
21+
{
22+
static const QByteArray HEADER = QByteArray::fromHex("04000400090028"); // Added for parsing
23+
static const QByteArray ENABLED = HEADER + QByteArray::fromHex("01000000");
24+
static const QByteArray DISABLED = HEADER + QByteArray::fromHex("02000000");
25+
static const QByteArray DATA_HEADER = QByteArray::fromHex("040004004B00020001"); // For received data
26+
}
27+
28+
// Connection Packets
29+
namespace Connection
30+
{
31+
static const QByteArray HANDSHAKE = QByteArray::fromHex("00000400010002000000000000000000");
32+
static const QByteArray SET_SPECIFIC_FEATURES = QByteArray::fromHex("040004004d00ff00000000000000");
33+
static const QByteArray REQUEST_NOTIFICATIONS = QByteArray::fromHex("040004000f00ffffffffff");
34+
static const QByteArray AIRPODS_DISCONNECTED = QByteArray::fromHex("00010000");
35+
}
36+
37+
// Phone Communication Packets
38+
namespace Phone
39+
{
40+
static const QByteArray NOTIFICATION = QByteArray::fromHex("00040001");
41+
static const QByteArray CONNECTED = QByteArray::fromHex("00010001");
42+
static const QByteArray DISCONNECTED = QByteArray::fromHex("00010000");
43+
static const QByteArray STATUS_REQUEST = QByteArray::fromHex("00020003");
44+
static const QByteArray DISCONNECT_REQUEST = QByteArray::fromHex("00020000");
45+
}
46+
47+
// Parsing Headers
48+
namespace Parse
49+
{
50+
static const QByteArray EAR_DETECTION = QByteArray::fromHex("040004000600");
51+
static const QByteArray BATTERY_STATUS = QByteArray::fromHex("040004000400");
52+
}
53+
}
54+
55+
#endif // AIRPODS_PACKETS_H

linux/main.cpp

Lines changed: 114 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include "main.h"
2+
#include "airpods_packets.h"
23

34
#define LOG_INFO(msg) qCInfo(airpodsApp) << "\033[32m" << msg << "\033[0m"
45
#define LOG_WARN(msg) qCWarning(airpodsApp) << "\033[33m" << msg << "\033[0m"
@@ -187,16 +188,18 @@ class AirPodsTrayApp : public QObject {
187188
QDBusConnection::systemBus().registerService("me.kavishdevar.aln");
188189
}
189190

190-
void notifyAndroidDevice() {
191-
if (phoneSocket && phoneSocket->isOpen()) {
192-
QByteArray notificationPacket = QByteArray::fromHex("00040001");
193-
phoneSocket->write(notificationPacket);
194-
LOG_DEBUG("Sent notification packet to Android: " << notificationPacket.toHex());
195-
} else {
191+
void notifyAndroidDevice()
192+
{
193+
if (phoneSocket && phoneSocket->isOpen())
194+
{
195+
phoneSocket->write(AirPodsPackets::Phone::NOTIFICATION);
196+
LOG_DEBUG("Sent notification packet to Android: " << AirPodsPackets::Phone::NOTIFICATION.toHex());
197+
}
198+
else
199+
{
196200
LOG_WARN("Phone socket is not open, cannot send notification packet");
197201
}
198202
}
199-
200203
void onNameOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner) {
201204
if (name == "org.bluez") {
202205
if (newOwner.isEmpty()) {
@@ -257,38 +260,48 @@ public slots:
257260
}
258261
}
259262

260-
void setNoiseControlMode(NoiseControlMode mode) {
263+
void setNoiseControlMode(NoiseControlMode mode)
264+
{
261265
LOG_INFO("Setting noise control mode to: " << mode);
262266
QByteArray packet;
263-
switch (mode) {
264-
case Off:
265-
packet = QByteArray::fromHex("0400040009000D01000000");
266-
break;
267-
case NoiseCancellation:
268-
packet = QByteArray::fromHex("0400040009000D02000000");
269-
break;
270-
case Transparency:
271-
packet = QByteArray::fromHex("0400040009000D03000000");
272-
break;
273-
case Adaptive:
274-
packet = QByteArray::fromHex("0400040009000D04000000");
275-
break;
267+
switch (mode)
268+
{
269+
case Off:
270+
packet = AirPodsPackets::NoiseControl::OFF;
271+
break;
272+
case NoiseCancellation:
273+
packet = AirPodsPackets::NoiseControl::NOISE_CANCELLATION;
274+
break;
275+
case Transparency:
276+
packet = AirPodsPackets::NoiseControl::TRANSPARENCY;
277+
break;
278+
case Adaptive:
279+
packet = AirPodsPackets::NoiseControl::ADAPTIVE;
280+
break;
276281
}
277-
if (socket && socket->isOpen()) {
282+
if (socket && socket->isOpen())
283+
{
278284
socket->write(packet);
279285
LOG_DEBUG("Noise control mode packet written: " << packet.toHex());
280-
} else {
286+
}
287+
else
288+
{
281289
LOG_ERROR("Socket is not open, cannot write noise control mode packet");
282290
}
283291
}
284292

285-
void setConversationalAwareness(bool enabled) {
293+
void setConversationalAwareness(bool enabled)
294+
{
286295
LOG_INFO("Setting conversational awareness to: " << (enabled ? "enabled" : "disabled"));
287-
QByteArray packet = enabled ? QByteArray::fromHex("0400040009002801000000") : QByteArray::fromHex("0400040009002802000000");
288-
if (socket && socket->isOpen()) {
296+
QByteArray packet = enabled ? AirPodsPackets::ConversationalAwareness::ENABLED
297+
: AirPodsPackets::ConversationalAwareness::DISABLED;
298+
if (socket && socket->isOpen())
299+
{
289300
socket->write(packet);
290301
LOG_DEBUG("Conversational awareness packet written: " << packet.toHex());
291-
} else {
302+
}
303+
else
304+
{
292305
LOG_ERROR("Socket is not open, cannot write conversational awareness packet");
293306
}
294307
}
@@ -468,17 +481,19 @@ public slots:
468481
}
469482
}
470483

471-
void onDeviceDisconnected(const QBluetoothAddress &address) {
484+
void onDeviceDisconnected(const QBluetoothAddress &address)
485+
{
472486
LOG_INFO("Device disconnected: " << address.toString());
473-
if (socket) {
487+
if (socket)
488+
{
474489
LOG_WARN("Socket is still open, closing it");
475490
socket->close();
476491
socket = nullptr;
477492
}
478-
if (phoneSocket && phoneSocket->isOpen()) {
479-
QByteArray airpodsDisconnectedPacket = QByteArray::fromHex("00010000");
480-
phoneSocket->write(airpodsDisconnectedPacket);
481-
LOG_DEBUG("AIRPODS_DISCONNECTED packet written: " << airpodsDisconnectedPacket.toHex());
493+
if (phoneSocket && phoneSocket->isOpen())
494+
{
495+
phoneSocket->write(AirPodsPackets::Connection::AIRPODS_DISCONNECTED);
496+
LOG_DEBUG("AIRPODS_DISCONNECTED packet written: " << AirPodsPackets::Connection::AIRPODS_DISCONNECTED.toHex());
482497
}
483498
}
484499

@@ -494,9 +509,9 @@ public slots:
494509
LOG_INFO("Connected to device, sending initial packets");
495510
discoveryAgent->stop();
496511

497-
QByteArray handshakePacket = QByteArray::fromHex("00000400010002000000000000000000");
498-
QByteArray setSpecificFeaturesPacket = QByteArray::fromHex("040004004d00ff00000000000000");
499-
QByteArray requestNotificationsPacket = QByteArray::fromHex("040004000f00ffffffffff");
512+
QByteArray handshakePacket = AirPodsPackets::Connection::HANDSHAKE;
513+
QByteArray setSpecificFeaturesPacket = AirPodsPackets::Connection::SET_SPECIFIC_FEATURES;
514+
QByteArray requestNotificationsPacket = AirPodsPackets::Connection::REQUEST_NOTIFICATIONS;
500515

501516
qint64 bytesWritten = localSocket->write(handshakePacket);
502517
LOG_DEBUG("Handshake packet written: " << handshakePacket.toHex() << ", bytes written: " << bytesWritten);
@@ -557,10 +572,14 @@ public slots:
557572
: "In case";
558573
}
559574

560-
void parseData(const QByteArray &data) {
575+
void parseData(const QByteArray &data)
576+
{
561577
LOG_DEBUG("Received: " << data.toHex());
562-
if (data.size() == 11 && data.startsWith(QByteArray::fromHex("0400040009000D"))) {
563-
quint8 rawMode = data[7] - 1;
578+
579+
// Noise Control Mode
580+
if (data.size() == 11 && data.startsWith(AirPodsPackets::NoiseControl::HEADER))
581+
{
582+
quint8 rawMode = data[7] - 1; // Offset still needed due to protocol
564583
if (rawMode >= NoiseControlMode::MinValue && rawMode <= NoiseControlMode::MaxValue)
565584
{
566585
NoiseControlMode mode = static_cast<NoiseControlMode>(rawMode);
@@ -571,30 +590,41 @@ public slots:
571590
{
572591
LOG_ERROR("Invalid noise control mode value received: " << rawMode);
573592
}
574-
} else if (data.size() == 8 && data.startsWith(QByteArray::fromHex("040004000600"))) {
593+
}
594+
// Ear Detection
595+
else if (data.size() == 8 && data.startsWith(AirPodsPackets::Parse::EAR_DETECTION))
596+
{
575597
char primary = data[6];
576598
char secondary = data[7];
577599
QString earDetectionStatus = QString("Primary: %1, Secondary: %2")
578-
.arg(getEarStatus(primary), getEarStatus(secondary));
600+
.arg(getEarStatus(primary), getEarStatus(secondary));
579601
LOG_INFO("Ear detection status: " << earDetectionStatus);
580602
emit earDetectionStatusChanged(earDetectionStatus);
581-
} else if (data.size() == 22 && data.startsWith(QByteArray::fromHex("040004000400"))) {
603+
}
604+
// Battery Status
605+
else if (data.size() == 22 && data.startsWith(AirPodsPackets::Parse::BATTERY_STATUS))
606+
{
582607
int leftLevel = data[9];
583608
int rightLevel = data[14];
584609
int caseLevel = data[19];
585610
QString batteryStatus = QString("Left: %1%, Right: %2%, Case: %3%")
586-
.arg(leftLevel)
587-
.arg(rightLevel)
588-
.arg(caseLevel);
611+
.arg(leftLevel)
612+
.arg(rightLevel)
613+
.arg(caseLevel);
589614
LOG_INFO("Battery status: " << batteryStatus);
590615
emit batteryStatusChanged(batteryStatus);
591-
592-
} else if (data.size() == 10 && data.startsWith(QByteArray::fromHex("040004004B00020001"))) {
616+
}
617+
// Conversational Awareness Data
618+
else if (data.size() == 10 && data.startsWith(AirPodsPackets::ConversationalAwareness::DATA_HEADER))
619+
{
593620
LOG_INFO("Received conversational awareness data");
594621
handleConversationalAwareness(data);
595622
}
623+
else
624+
{
625+
LOG_DEBUG("Unrecognized packet format: " << data.toHex());
626+
}
596627
}
597-
598628
void handleConversationalAwareness(const QByteArray &data) {
599629
LOG_DEBUG("Handling conversational awareness data: " << data.toHex());
600630
static int initialVolume = -1;
@@ -692,39 +722,52 @@ public slots:
692722
phoneSocket->connectToService(phoneAddress, QBluetoothUuid("1abbb9a4-10e4-4000-a75c-8953c5471342"));
693723
}
694724

695-
void relayPacketToPhone(const QByteArray &packet) {
696-
if (phoneSocket && phoneSocket->isOpen()) {
697-
QByteArray header = QByteArray::fromHex("00040001");
698-
phoneSocket->write(header + packet);
699-
} else {
725+
void relayPacketToPhone(const QByteArray &packet)
726+
{
727+
if (phoneSocket && phoneSocket->isOpen())
728+
{
729+
phoneSocket->write(AirPodsPackets::Phone::NOTIFICATION + packet);
730+
}
731+
else
732+
{
700733
connectToPhone();
701734
LOG_WARN("Phone socket is not open, cannot relay packet");
702735
}
703736
}
704737

705738
void handlePhonePacket(const QByteArray &packet) {
706-
if (packet.startsWith(QByteArray::fromHex("00040001"))) {
739+
if (packet.startsWith(AirPodsPackets::Phone::NOTIFICATION))
740+
{
707741
QByteArray airpodsPacket = packet.mid(4);
708742
if (socket && socket->isOpen()) {
709743
socket->write(airpodsPacket);
710744
LOG_DEBUG("Relayed packet to AirPods: " << airpodsPacket.toHex());
711745
} else {
712746
LOG_ERROR("Socket is not open, cannot relay packet to AirPods");
713747
}
714-
} else if (packet.startsWith(QByteArray::fromHex("00010001"))) {
748+
}
749+
else if (packet.startsWith(AirPodsPackets::Phone::CONNECTED))
750+
{
715751
LOG_INFO("AirPods connected");
716752
isConnectedLocally = true;
717753
CrossDevice.isAvailable = false;
718-
} else if (packet.startsWith(QByteArray::fromHex("00010000"))) {
754+
}
755+
else if (packet.startsWith(AirPodsPackets::Phone::DISCONNECTED))
756+
{
719757
LOG_INFO("AirPods disconnected");
720758
isConnectedLocally = false;
721759
CrossDevice.isAvailable = true;
722-
} else if (packet.startsWith(QByteArray::fromHex("00020003"))) {
760+
}
761+
else if (packet.startsWith(AirPodsPackets::Phone::STATUS_REQUEST))
762+
{
723763
LOG_INFO("Connection status request received");
724-
QByteArray response = (socket && socket->isOpen()) ? QByteArray::fromHex("00010001") : QByteArray::fromHex("00010000");
764+
QByteArray response = (socket && socket->isOpen()) ? AirPodsPackets::Phone::CONNECTED
765+
: AirPodsPackets::Phone::DISCONNECTED;
725766
phoneSocket->write(response);
726767
LOG_DEBUG("Sent connection status response: " << response.toHex());
727-
} else if (packet.startsWith(QByteArray::fromHex("00020000"))) {
768+
}
769+
else if (packet.startsWith(AirPodsPackets::Phone::DISCONNECT_REQUEST))
770+
{
728771
LOG_INFO("Disconnect request received");
729772
if (socket && socket->isOpen()) {
730773
socket->close();
@@ -737,7 +780,9 @@ public slots:
737780
isConnectedLocally = false;
738781
CrossDevice.isAvailable = true;
739782
}
740-
} else {
783+
}
784+
else
785+
{
741786
if (socket && socket->isOpen()) {
742787
socket->write(packet);
743788
LOG_DEBUG("Relayed packet to AirPods: " << packet.toHex());
@@ -787,12 +832,15 @@ public slots:
787832
playerctlProcess->start("playerctl", QStringList() << "--follow" << "status");
788833
}
789834

790-
void sendDisconnectRequestToAndroid() {
791-
if (phoneSocket && phoneSocket->isOpen()) {
792-
QByteArray disconnectRequest = QByteArray::fromHex("00020000");
793-
phoneSocket->write(disconnectRequest);
794-
LOG_DEBUG("Sent disconnect request to Android: " << disconnectRequest.toHex());
795-
} else {
835+
void sendDisconnectRequestToAndroid()
836+
{
837+
if (phoneSocket && phoneSocket->isOpen())
838+
{
839+
phoneSocket->write(AirPodsPackets::Phone::DISCONNECT_REQUEST);
840+
LOG_DEBUG("Sent disconnect request to Android: " << AirPodsPackets::Phone::DISCONNECT_REQUEST.toHex());
841+
}
842+
else
843+
{
796844
LOG_WARN("Phone socket is not open, cannot send disconnect request");
797845
}
798846
}

0 commit comments

Comments
 (0)