Nothing Ear (1): Read audio status from device and various fixes:

- add support for ANC-light mode
- handle multiple GBDeviceEvents
- add fake fw and hw versions to make DBHelper happy
- fix battery charge detection logic
- extract some strings to resources
This commit is contained in:
Daniele Gobbetti 2021-10-07 20:22:41 +02:00
parent ebe2558690
commit 9ad7e210b7
4 changed files with 66 additions and 14 deletions

View File

@ -7,13 +7,18 @@ import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryState;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
@ -26,8 +31,9 @@ public class NothingProtocol extends GBDeviceProtocol {
final UUID UUID_DEVICE_CTRL = UUID.fromString("aeac4a03-dff5-498f-843a-34487cf133eb");
private boolean isFirstExchange = true;
public static final byte CONTROL_DEVICE_TYPE_TWS_HEADSET = 1;
private static final byte CONTROL_DEVICE_TYPE_TWS_HEADSET = 1;
private static final int CONTROL_CRC = 0x20;
@ -44,15 +50,25 @@ public class NothingProtocol extends GBDeviceProtocol {
//incoming
private static final short battery_status = (short) 0xe001;
private static final short battery_status2 = (short) 0xc007;
private static final short audio_mode_status = (short) 0xc01e;
private static final short unk_maybe_ack = (short) 0xf002;
private static final short unk_close_case = (short) 0xe002; //sent twice when the case is closed with earphones in
//outgoing
private static final short find_device = (short) 0xf002;
private static final short in_ear_detection = (short) 0xf004;
private static final short audio_mode = (short) 0xf00f;
@Override
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
List<GBDeviceEvent> devEvts = new ArrayList<>();
if (isFirstExchange) {
isFirstExchange = false;
devEvts.add(new GBDeviceEventVersionInfo()); //TODO: this is a weird hack to make the DBHelper happy. Replace with proper firmware detection
}
ByteBuffer incoming = ByteBuffer.wrap(responseData);
incoming.order(ByteOrder.LITTLE_ENDIAN);
@ -81,7 +97,11 @@ public class NothingProtocol extends GBDeviceProtocol {
switch (getRequestCommand(command)) {
case battery_status:
case battery_status2:
return handleBatteryInfo(payload);
devEvts.add(handleBatteryInfo(payload));
break;
case audio_mode_status:
devEvts.add(handleAudioModeStatus(payload));
break;
case unk_maybe_ack:
LOG.debug("received ack");
@ -94,14 +114,14 @@ public class NothingProtocol extends GBDeviceProtocol {
LOG.debug("Incoming message - control:" + control + " requestCommand: " + (getRequestCommand(command) & 0xffff) + "length: " + length + " dump: " + hexdump(responseData));
}
return null;
return devEvts.toArray(new GBDeviceEvent[devEvts.size()]);
}
boolean isCrcNeeded(short control) {
return (control & CONTROL_CRC) != 0;
}
private byte[] encodeMessage(short control, short command, byte[] payload) {
byte[] encodeMessage(short control, short command, byte[] payload) {
ByteBuffer msgBuf = ByteBuffer.allocate(8 + payload.length);
msgBuf.order(ByteOrder.LITTLE_ENDIAN);
@ -125,7 +145,30 @@ public class NothingProtocol extends GBDeviceProtocol {
}
byte[] encodeBatteryStatusReq() {
return encodeMessage((short) 0x120, (short) 0xc007, new byte[]{});
return encodeMessage((short) 0x5120, battery_status2, new byte[]{});
}
byte[] encodeAudioModeStatusReq() {
return encodeMessage((short) 0x120, audio_mode_status, new byte[]{});
}
//TODO: unify mapping between bytes and strings in the following two functions
private GBDeviceEvent handleAudioModeStatus(byte[] payload) {
SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
SharedPreferences.Editor editor = prefs.edit();
if (Arrays.equals(payload, new byte[]{0x01, 0x01, 0x00})) {
editor.putString(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE, "anc").apply();
} else if (Arrays.equals(payload, new byte[]{0x01, 0x03, 0x00})) {
editor.putString(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE, "anc-light").apply();
} else if (Arrays.equals(payload, new byte[]{0x01, 0x05, 0x00})) {
editor.putString(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE, "off").apply();
} else if (Arrays.equals(payload, new byte[]{0x01, 0x07, 0x00})) {
editor.putString(DeviceSettingsPreferenceConst.PREF_NOTHING_EAR1_AUDIOMODE, "transparency").apply();
} else {
LOG.warn("Unknown audio mode. Payload: " + hexdump(payload));
}
return null;
}
byte[] encodeAudioMode(String desired) {
@ -135,19 +178,23 @@ public class NothingProtocol extends GBDeviceProtocol {
case "anc":
payload[1] = 0x01;
break;
case "anc-light":
payload[1] = 0x03;
break;
case "transparency":
payload[1] = 0x07;
break;
case "off":
default:
}
return encodeMessage((short) 0x120, (short) 0xf00f, payload);
return encodeMessage((short) 0x120, audio_mode, payload);
}
@Override
public byte[] encodeFindDevice(boolean start) {
byte payload = (byte) (start ? 0x01 : 0x00);
return encodeMessage((short) 0x120, (short) 0xf002, new byte[]{payload});
return encodeMessage((short) 0x120, find_device, new byte[]{payload});
}
@Override
@ -175,10 +222,11 @@ public class NothingProtocol extends GBDeviceProtocol {
public byte[] encodeSetTime() {
// This are earphones, there is no time to set here. However this method gets called soon
// after connecting, hence we use it to perform some initializations.
return encodeBatteryStatusReq();
// TODO: Find a way to send more requests during the first connection
return encodeAudioModeStatusReq();
}
private GBDeviceEvent[] handleBatteryInfo(byte[] payload) {
private GBDeviceEvent handleBatteryInfo(byte[] payload) {
//LOG.debug("Battery payload: " + hexdump(payload));
/* payload:
@ -202,14 +250,14 @@ public class NothingProtocol extends GBDeviceProtocol {
for (int i = 0; i < numBatteries; i++) {
evBattery.level += (short) ((payload[2 + 2 * i] & MASK_BATTERY) / numBatteries);
if (!batteryCharging)
batteryCharging = ((payload[2 + 2 * i]) & MASK_BATTERY_CHARGING) == 1;
batteryCharging = ((payload[2 + 2 * i] & MASK_BATTERY_CHARGING) == MASK_BATTERY_CHARGING);
//LOG.debug("single battery level: " + hexdump(payload, 2+2*i,1) +"-"+ ((payload[2+2*i] & 0xff))+":" + evBattery.level);
}
evBattery.state = BatteryState.UNKNOWN;
evBattery.state = batteryCharging ? BatteryState.BATTERY_CHARGING : evBattery.state;
return new GBDeviceEvent[]{evBattery};
return evBattery;
}
private short getRequestCommand(short command) {

View File

@ -1762,6 +1762,7 @@
<string-array name="nothing_ear1_audio_mode">
<item>anc</item>
<item>anc-light</item>
<item>transparency</item>
<item>off</item>
</string-array>

View File

@ -1272,4 +1272,7 @@
<string name="prefs_autoheartrate_sleep">Take measurements during sleep</string>
<string name="prefs_autoheartrate_interval">Frequency of measurements</string>
<string name="devicetype_nothingear1">Nothing Ear (1)</string>
<string name="nothing_prefs_inear_summary">Play/pause the music depending if you wear the earbuds</string>
<string name="nothing_prefs_inear_title">In-Ear detection</string>
<string name="nothing_prefs_audiomode_title">Audio mode</string>
</resources>

View File

@ -4,13 +4,13 @@
android:defaultValue="true"
android:icon="@drawable/ic_extension"
android:key="pref_nothing_inear_detection"
android:summary="Play/pause the music depending if you wear the earbuds"
android:title="In-Ear detection" />
android:summary="@string/nothing_prefs_inear_summary"
android:title="@string/nothing_prefs_inear_title" />
<ListPreference
android:icon="@drawable/ic_extension"
android:entryValues="@array/nothing_ear1_audio_mode"
android:entries="@array/nothing_ear1_audio_mode"
android:key="pref_nothing_audiomode"
android:summary="%s"
android:title="Audio mode" />
android:title="@string/nothing_prefs_audiomode_title" />
</androidx.preference.PreferenceScreen>