Skip to content

Commit f062eb4

Browse files
authored
linux: hearing aid support (#230)
* linux: add hearing aid it's just a simple python script, with a toggle in the main app. i dont want to mess with the main app because it uses ATT instead of the AACP protocol which is implemented in the app. * linux: implement adding hearing aid test results * docs: add linux screenshot * docs: add linux hearing aid script * linux: add reset button for hearing aid adjustments * linux: remove MAC address logging for security
1 parent 28ffd21 commit f062eb4

File tree

7 files changed

+570
-1
lines changed

7 files changed

+570
-1
lines changed

linux/Main.qml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,13 @@ ApplicationWindow {
156156
checked: airPodsTrayApp.deviceInfo.conversationalAwareness
157157
onCheckedChanged: airPodsTrayApp.setConversationalAwareness(checked)
158158
}
159+
160+
Switch {
161+
visible: airPodsTrayApp.airpodsConnected
162+
text: "Hearing Aid"
163+
checked: airPodsTrayApp.deviceInfo.hearingAidEnabled
164+
onCheckedChanged: airPodsTrayApp.setHearingAidEnabled(checked)
165+
}
159166
}
160167

161168
RoundButton {

linux/README.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ A native Linux application to control your AirPods, with support for:
66
- Conversational Awareness
77
- Battery monitoring
88
- Auto play/pause on ear detection
9-
- Seamless handoff between phone and PC
9+
- Hearing Aid features
10+
- Supports adjusting hearing aid- amplification, balance, tone, ambient noise reduction, own voice amplification, and conversation boost
11+
- Supports setting the values for left and right hearing aids (this is not a hearing test! you need to have an audiogram to set the values)
12+
- Seamless handoff between Android and Linux
1013

1114
## Prerequisites
1215

@@ -97,3 +100,35 @@ systemctl --user enable --now mpris-proxy
97100
- Switch between noise control modes
98101
- View battery levels
99102
- Control playback
103+
104+
## Hearing Aid
105+
106+
To use hearing aid features, you need to have an audiogram. To enable/disable hearing aid, you can use the toggle in the main app. But, to adjust the settings and set the audiogram, you need to use a different script which is located in this folder as `hearing_aid.py`. You can run it with:
107+
108+
```bash
109+
python3 hearing_aid.py
110+
```
111+
112+
The script will load the current settings from the AirPods and allow you to adjust them. You can set the audiogram by providing the values for 8 frequencies (250Hz, 500Hz, 1kHz, 2kHz, 3kHz, 4kHz, 6kHz, 8kHz) for both left and right ears. There are also options to adjust amplification, balance, tone, ambient noise reduction, own voice amplification, and conversation boost.
113+
114+
AirPods check for the DeviceID characteristic to see if the connected device is an Apple device and only then allow hearing aid features. To set the DeviceID characteristic, you need to add this line to your bluetooth configuration file (usually located at `/etc/bluetooth/main.conf`):
115+
116+
```
117+
DeviceID = bluetooth:004C:0000:0000
118+
```
119+
120+
Then, restart the bluetooth service:
121+
122+
```bash
123+
sudo systemctl restart bluetooth
124+
```
125+
126+
Here, you might need to re-pair your AirPods because they seem to cache this info.
127+
128+
### Troubleshooting
129+
130+
It is possible that the AirPods disconnect after a short period of time and play the disconnect sound. This is likely due to the AirPods expecting some information from an Apple device. Since I have not implemented everything that an Apple device does, the AirPods may disconnect. You don't need to reconnect them manually; the script will handle reconnection automatically for hearing aid features. So, once you are done setting the hearing aid features, change back the `DeviceID` to whatever it was before.
131+
132+
### Why a separate script?
133+
134+
Because I discovered that QBluetooth doesn't support connecting to a socket with its PSM, only a UUID can be used. I could add a dependency on BlueZ, but then having two bluetooth interfaces seems unnecessary. So, I decided to use a separate script for hearing aid features. In the future, QBluetooth will be replaced with BlueZ native calls, and then everything will be in one application.

linux/airpods_packets.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,16 @@ namespace AirPodsPackets
107107
inline std::optional<bool> parseState(const QByteArray &data) { return Type::parseState(data); }
108108
}
109109

110+
// Hearing Aid
111+
namespace HearingAid
112+
{
113+
using Type = BasicControlCommand<0x2C>;
114+
static const QByteArray ENABLED = Type::ENABLED;
115+
static const QByteArray DISABLED = Type::DISABLED;
116+
static const QByteArray HEADER = Type::HEADER;
117+
inline std::optional<bool> parseState(const QByteArray &data) { return Type::parseState(data); }
118+
}
119+
110120
// Allow Off Option
111121
namespace AllowOffOption
112122
{

linux/deviceinfo.hpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ class DeviceInfo : public QObject
1515
Q_PROPERTY(QString batteryStatus READ batteryStatus WRITE setBatteryStatus NOTIFY batteryStatusChanged)
1616
Q_PROPERTY(int noiseControlMode READ noiseControlModeInt WRITE setNoiseControlModeInt NOTIFY noiseControlModeChangedInt)
1717
Q_PROPERTY(bool conversationalAwareness READ conversationalAwareness WRITE setConversationalAwareness NOTIFY conversationalAwarenessChanged)
18+
Q_PROPERTY(bool hearingAidEnabled READ hearingAidEnabled WRITE setHearingAidEnabled NOTIFY hearingAidEnabledChanged)
1819
Q_PROPERTY(int adaptiveNoiseLevel READ adaptiveNoiseLevel WRITE setAdaptiveNoiseLevel NOTIFY adaptiveNoiseLevelChanged)
1920
Q_PROPERTY(QString deviceName READ deviceName WRITE setDeviceName NOTIFY deviceNameChanged)
2021
Q_PROPERTY(Battery *battery READ getBattery CONSTANT)
@@ -67,6 +68,16 @@ class DeviceInfo : public QObject
6768
}
6869
}
6970

71+
bool hearingAidEnabled() const { return m_hearingAidEnabled; }
72+
void setHearingAidEnabled(bool enabled)
73+
{
74+
if (m_hearingAidEnabled != enabled)
75+
{
76+
m_hearingAidEnabled = enabled;
77+
emit hearingAidEnabledChanged(enabled);
78+
}
79+
}
80+
7081
int adaptiveNoiseLevel() const { return m_adaptiveNoiseLevel; }
7182
void setAdaptiveNoiseLevel(int level)
7283
{
@@ -159,6 +170,7 @@ class DeviceInfo : public QObject
159170
setNoiseControlMode(NoiseControlMode::Off);
160171
setBluetoothAddress("");
161172
getEarDetection()->reset();
173+
setHearingAidEnabled(false);
162174
}
163175

164176
void saveToSettings(QSettings &settings)
@@ -168,6 +180,7 @@ class DeviceInfo : public QObject
168180
settings.setValue("model", static_cast<int>(model()));
169181
settings.setValue("magicAccIRK", magicAccIRK());
170182
settings.setValue("magicAccEncKey", magicAccEncKey());
183+
settings.setValue("hearingAidEnabled", hearingAidEnabled());
171184
settings.endGroup();
172185
}
173186
void loadFromSettings(const QSettings &settings)
@@ -176,6 +189,7 @@ class DeviceInfo : public QObject
176189
setModel(static_cast<AirPodsModel>(settings.value("DeviceInfo/model", (int)(AirPodsModel::Unknown)).toInt()));
177190
setMagicAccIRK(settings.value("DeviceInfo/magicAccIRK", QByteArray()).toByteArray());
178191
setMagicAccEncKey(settings.value("DeviceInfo/magicAccEncKey", QByteArray()).toByteArray());
192+
setHearingAidEnabled(settings.value("DeviceInfo/hearingAidEnabled", false).toBool());
179193
}
180194

181195
void updateBatteryStatus()
@@ -191,6 +205,7 @@ class DeviceInfo : public QObject
191205
void noiseControlModeChanged(NoiseControlMode mode);
192206
void noiseControlModeChangedInt(int mode);
193207
void conversationalAwarenessChanged(bool enabled);
208+
void hearingAidEnabledChanged(bool enabled);
194209
void adaptiveNoiseLevelChanged(int level);
195210
void deviceNameChanged(const QString &name);
196211
void primaryChanged();
@@ -202,6 +217,7 @@ class DeviceInfo : public QObject
202217
QString m_batteryStatus;
203218
NoiseControlMode m_noiseControlMode = NoiseControlMode::Transparency;
204219
bool m_conversationalAwareness = false;
220+
bool m_hearingAidEnabled = false;
205221
int m_adaptiveNoiseLevel = 50;
206222
QString m_deviceName;
207223
Battery *m_battery;

0 commit comments

Comments
 (0)