Add ANC and Passthrough options to Bowers and Wilkins P Series (#4297)

Co-authored-by: mvn23 <schopdiedwaas@gmail.com>
Co-committed-by: mvn23 <schopdiedwaas@gmail.com>
This commit is contained in:
mvn23 2024-11-09 15:29:44 +00:00 committed by José Rebelo
parent 7c1d44fcd3
commit 6bb93bef89
9 changed files with 220 additions and 11 deletions

View File

@ -106,6 +106,11 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_DEVICE_INTERNET_ACCESS = "device_internet_access";
public static final String PREF_DEVICE_INTENTS = "device_intents";
public static final String PREF_ACTIVE_NOISE_CANCELLING_TOGGLE = "active_noise_cancelling_toggle";
public static final String PREF_BANDW_PSERIES_VPT_ENABLED = "bandw_pseries_vpt_enabled";
public static final String PREF_BANDW_PSERIES_VPT_LEVEL = "bandw_pseries_vpt_level";
public static final String PREF_BANDW_PSERIES_GUI_VPT_LEVEL = "bandw_pseries_gui_vpt_level";
public static final String PREF_BANGLEJS_TEXT_BITMAP = "banglejs_text_bitmap";
public static final String PREF_BANGLEJS_TEXT_BITMAP_SIZE = "banglejs_txt_bitmap_size";
public static final String PREF_BANGLEJS_WEBVIEW_URL = "banglejs_webview_url";

View File

@ -611,6 +611,9 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
addPreferenceHandlerFor(PREF_SLEEP_MODE_SLEEP_SCREEN);
addPreferenceHandlerFor(PREF_SLEEP_MODE_SMART_ENABLE);
addPreferenceHandlerFor(PREF_ACTIVE_NOISE_CANCELLING_TOGGLE);
addPreferenceHandlerFor(PREF_BANDW_PSERIES_GUI_VPT_LEVEL);
addPreferenceHandlerFor(PREF_HYBRID_HR_DRAW_WIDGET_CIRCLES);
addPreferenceHandlerFor(PREF_HYBRID_HR_FORCE_WHITE_COLOR);
addPreferenceHandlerFor(PREF_HYBRID_HR_SAVE_RAW_ACTIVITY_FILES);

View File

@ -5,7 +5,7 @@ import androidx.annotation.NonNull;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLEDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@ -13,7 +13,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.bandwpseries.BandWPSeriesDeviceSupport;
public class BandWPSeriesDeviceCoordinator extends AbstractDeviceCoordinator {
public class BandWPSeriesDeviceCoordinator extends AbstractBLEDeviceCoordinator {
@Override
public int getDeviceNameResource() {
return R.string.devicetype_bandw_pseries;
@ -51,5 +51,12 @@ public class BandWPSeriesDeviceCoordinator extends AbstractDeviceCoordinator {
return new BatteryConfig[]{battery0, battery1, battery2};
}
@Override
public int[] getSupportedDeviceSpecificSettings(GBDevice device) {
return new int[] {
R.xml.devicesettings_active_noise_cancelling_toggle,
R.xml.devicesettings_bandw_pseries
};
}
}

View File

@ -18,12 +18,18 @@ public class BandWBLEProfile<T extends AbstractBTLEDeviceSupport> extends Abstra
public static final String ACTION_DEVICE_INFO = ACTION_PREFIX + "DEVICE_INFO";
public static final String EXTRA_DEVICE_INFO = "DEVICE_INFO";
public static final byte ANC_MODE_OFF = 0x01;
public static final byte ANC_MODE_ON = 0x03;
public static final UUID UUID_RPC_REQUEST_CHARACTERISTIC = UUID.fromString("ada50ce9-67b8-4a97-9d8e-37e1d083156c");
public BandWBLEProfile(final T support) {
super(support);
}
public void requestAncModeState(final TransactionBuilder builder) {
sendRequest(builder, (byte) 0x03, (byte) 0x01);
}
public void requestDeviceName(final TransactionBuilder builder) {
sendRequest(builder, (byte) 0x05, (byte) 0x01);
}
@ -36,6 +42,29 @@ public class BandWBLEProfile<T extends AbstractBTLEDeviceSupport> extends Abstra
sendRequest(builder, (byte) 0x08, (byte) 0x17);
}
public void requestVptEnabled(final TransactionBuilder builder) {
sendRequest(builder, (byte) 0x03, (byte) 0x05);
}
public void requestVptLevel(final TransactionBuilder builder) {
sendRequest(builder, (byte) 0x03, (byte) 0x03);
}
public void setAncModeState(final TransactionBuilder builder, final boolean mode) throws IOException {
BandWPSeriesRequest req = new BandWPSeriesRequest((byte) 0x03, (byte) 0x02).addToPayload(mode ? ANC_MODE_ON : ANC_MODE_OFF);
builder.write(getCharacteristic(UUID_RPC_REQUEST_CHARACTERISTIC), req.finishAndGetBytes());
}
public void setVptLevel(final TransactionBuilder builder, final int level) throws IOException {
BandWPSeriesRequest req = new BandWPSeriesRequest((byte) 0x03, (byte) 0x04).addToPayload(level);
builder.write(getCharacteristic(UUID_RPC_REQUEST_CHARACTERISTIC), req.finishAndGetBytes());
}
public void setVptEnabled(final TransactionBuilder builder, final boolean mode) throws IOException {
BandWPSeriesRequest req = new BandWPSeriesRequest((byte) 0x03, (byte) 0x06).addToPayload(mode);
builder.write(getCharacteristic(UUID_RPC_REQUEST_CHARACTERISTIC), req.finishAndGetBytes());
}
private void sendRequest(final TransactionBuilder builder, byte namespace, byte commandID) {
BandWPSeriesRequest req;
try {

View File

@ -1,15 +1,25 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.bandwpseries;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_ACTIVE_NOISE_CANCELLING_TOGGLE;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANDW_PSERIES_GUI_VPT_LEVEL;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANDW_PSERIES_VPT_ENABLED;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_BANDW_PSERIES_VPT_LEVEL;
import static nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.BATTERY_UNKNOWN;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.bandwpseries.BandWBLEProfile.ANC_MODE_ON;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGatt;
import android.content.SharedPreferences.Editor;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Arrays;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
@ -18,6 +28,7 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class BandWPSeriesDeviceSupport extends AbstractBTLEDeviceSupport {
@ -25,6 +36,7 @@ public class BandWPSeriesDeviceSupport extends AbstractBTLEDeviceSupport {
private static final UUID UUID_RPC_SERVICE = UUID.fromString("85ba93a5-09ac-439a-8cc4-1c3f0cb4f29f");
private static final UUID UUID_RPC_RESPONSE_CHARACTERISTIC = UUID.fromString("cb909093-3559-4b0c-9a7f-3f1773122fdc");
private static final UUID UUID_RPC_NOTIFICATION_CHARACTERISTIC = UUID.fromString("df55d475-9a32-457a-9e20-38cf14e853fb");
private final BandWBLEProfile<BandWPSeriesDeviceSupport> BandWBLEProfile;
private final GBDeviceEventBatteryInfo[] batteryInfo = new GBDeviceEventBatteryInfo[3];
@ -59,9 +71,13 @@ public class BandWPSeriesDeviceSupport extends AbstractBTLEDeviceSupport {
builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext()));
builder.notify(getCharacteristic(UUID_RPC_RESPONSE_CHARACTERISTIC), true);
builder.notify(getCharacteristic(UUID_RPC_NOTIFICATION_CHARACTERISTIC), true);
BandWBLEProfile.requestFirmware(builder);
BandWBLEProfile.requestDeviceName(builder);
BandWBLEProfile.requestBatteryLevels(builder);
BandWBLEProfile.requestAncModeState(builder);
BandWBLEProfile.requestVptEnabled(builder);
BandWBLEProfile.requestVptLevel(builder);
return builder;
}
@ -70,7 +86,7 @@ public class BandWPSeriesDeviceSupport extends AbstractBTLEDeviceSupport {
UUID characteristicUUID = characteristic.getUuid();
if (UUID_RPC_RESPONSE_CHARACTERISTIC.equals(characteristicUUID)) {
if (UUID_RPC_RESPONSE_CHARACTERISTIC.equals(characteristicUUID) || UUID_RPC_NOTIFICATION_CHARACTERISTIC.equals(characteristicUUID)) {
return handleRPCResponse(characteristic);
}
return false;
@ -91,6 +107,20 @@ public class BandWPSeriesDeviceSupport extends AbstractBTLEDeviceSupport {
if (response.commandId == 0x01) {
return handleFirmwareVersionResponse(response);
}
} else if (response.namespace == 0x03) {
switch (response.commandId) {
case 0x01:
return handleGetAncModeStateResponse(response);
case 0x02:
case 0x04:
return getIntResponseStatus(response);
case 0x03:
return handleGetVptLevelResponse(response);
case 0x05:
return handleGetVptEnabledResponse(response);
case 0x06:
return getBooleanResponseStatus(response);
}
} else if (response.namespace == 0x05) {
if (response.commandId == 0x01) {
return handleDeviceNameResponse(response);
@ -103,6 +133,24 @@ public class BandWPSeriesDeviceSupport extends AbstractBTLEDeviceSupport {
return true;
}
private boolean handleGetAncModeStateResponse(BandWPSeriesResponse response) {
if (!response.messageType.hasPayload) {
GB.toast("No payload in response!", Toast.LENGTH_SHORT, GB.ERROR);
return false;
}
int payloadValue;
try {
payloadValue = response.payloadUnpacker.unpackInt();
} catch (IOException e) {
GB.toast("Could not extract ancMode from payload: " + Arrays.toString(response.payload), Toast.LENGTH_SHORT, GB.ERROR);
return false;
}
Editor editor = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).edit();
editor.putBoolean(PREF_ACTIVE_NOISE_CANCELLING_TOGGLE, payloadValue == ANC_MODE_ON);
editor.apply();
return true;
}
private boolean handleBatteryLevels(BandWPSeriesResponse response) {
int[] levels = response.getPayloadFixArray();
if (levels == null) {
@ -146,8 +194,92 @@ public class BandWPSeriesDeviceSupport extends AbstractBTLEDeviceSupport {
return true;
}
private boolean handleGetVptEnabledResponse(BandWPSeriesResponse response) {
if (!response.messageType.hasPayload) {
GB.toast("No payload in response!", Toast.LENGTH_SHORT, GB.ERROR);
return false;
}
boolean payloadValue;
try {
payloadValue = response.payloadUnpacker.unpackBoolean();
} catch (IOException e) {
GB.toast("Could not extract vptEnabled from payload: " + Arrays.toString(response.payload), Toast.LENGTH_SHORT, GB.ERROR);
return false;
}
int vptLevel = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getInt(PREF_BANDW_PSERIES_VPT_LEVEL, 0);
Editor editor = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).edit();
editor.putBoolean(PREF_BANDW_PSERIES_VPT_ENABLED, payloadValue);
editor.putInt(PREF_BANDW_PSERIES_GUI_VPT_LEVEL, payloadValue ? vptLevel + 1 : 0);
editor.apply();
return true;
}
private boolean handleGetVptLevelResponse(BandWPSeriesResponse response) {
if (!response.messageType.hasPayload) {
GB.toast("No payload in response!", Toast.LENGTH_SHORT, GB.ERROR);
return false;
}
int payloadValue;
try {
payloadValue = response.payloadUnpacker.unpackInt();
} catch (IOException e) {
GB.toast("Could not extract vptLevel from payload: " + Arrays.toString(response.payload), Toast.LENGTH_SHORT, GB.ERROR);
return false;
}
boolean vptEnabled = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(PREF_BANDW_PSERIES_VPT_ENABLED, false);
Editor editor = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).edit();
editor.putInt(PREF_BANDW_PSERIES_VPT_LEVEL, payloadValue);
editor.putInt(PREF_BANDW_PSERIES_GUI_VPT_LEVEL, vptEnabled ? payloadValue + 1 : 0);
editor.apply();
return true;
}
public void onSendConfiguration(String config) {
try {
TransactionBuilder builder = performInitialized("sendConfig");
switch (config) {
case PREF_ACTIVE_NOISE_CANCELLING_TOGGLE:
boolean ancMode = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getBoolean(PREF_ACTIVE_NOISE_CANCELLING_TOGGLE, true);
BandWBLEProfile.setAncModeState(builder, ancMode);
break;
case PREF_BANDW_PSERIES_GUI_VPT_LEVEL:
int level = GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()).getInt(PREF_BANDW_PSERIES_GUI_VPT_LEVEL, 0);
BandWBLEProfile.setVptEnabled(builder, level != 0);
if (level != 0) {
BandWBLEProfile.setVptLevel(builder, level - 1);
}
break;
}
performImmediately(builder);
} catch (IOException e) {
GB.toast("Failed to send settings update", Toast.LENGTH_SHORT, GB.ERROR);
}
}
@Override
public boolean useAutoConnect() {
return true;
}
private boolean getBooleanResponseStatus(BandWPSeriesResponse response) {
boolean payloadValue;
try {
payloadValue = response.payloadUnpacker.unpackBoolean();
} catch (IOException e) {
GB.toast("Could not extract response from payload: " + Arrays.toString(response.payload), Toast.LENGTH_SHORT, GB.ERROR);
return false;
}
return payloadValue;
}
private boolean getIntResponseStatus(BandWPSeriesResponse response) {
int payloadValue;
try {
payloadValue = response.payloadUnpacker.unpackInt();
} catch (IOException e) {
GB.toast("Could not extract response from payload: " + Arrays.toString(response.payload), Toast.LENGTH_SHORT, GB.ERROR);
return false;
}
return payloadValue == 0;
}
}

View File

@ -21,7 +21,6 @@ public class BandWPSeriesRequest {
messageType = BandWMessageType.REQUEST_WITHOUT_PAYLOAD;
namespace = mNamespace;
commandId = mCommandId;
payloadPacker.packInt(0);
}
public BandWPSeriesRequest addToPayload(int value) throws IOException {
@ -36,6 +35,12 @@ public class BandWPSeriesRequest {
return this;
}
public BandWPSeriesRequest addToPayload(boolean value) throws IOException {
payloadPacker.packBoolean(value);
messageType = BandWMessageType.REQUEST_WITH_PAYLOAD;
return this;
}
public BandWPSeriesRequest addToPayload(String value) throws IOException {
payloadPacker.packString(value);
messageType = BandWMessageType.REQUEST_WITH_PAYLOAD;
@ -43,13 +48,15 @@ public class BandWPSeriesRequest {
}
public byte[] finishAndGetBytes() {
byte len = (byte) ((this.messageType == BandWMessageType.REQUEST_WITHOUT_PAYLOAD) ? 4 : 4 + payloadPacker.getBufferSize());
byte[] payload = payloadPacker.toByteArray();
byte len = (byte) ((this.messageType == BandWMessageType.REQUEST_WITHOUT_PAYLOAD) ? 4 : 6 + payload.length);
byte[] out = addMessageType(new byte[len+1], messageType.value);
out[0] = len;
out[3] = commandId;
out[4] = namespace;
if (messageType == BandWMessageType.REQUEST_WITH_PAYLOAD) {
System.arraycopy(payloadPacker.toByteArray(), 0, out, 5, len - 5);
addShort(out, 5, payload.length);
System.arraycopy(payload, 0, out, 7, payload.length);
}
try {
payloadPacker.close();
@ -60,11 +67,14 @@ public class BandWPSeriesRequest {
}
private byte[] addMessageType(byte[] target, int value) {
byte valueLo = (byte) (value & 0xff);
byte valueHi = (byte) (value >> 8);
target[1] = valueLo;
target[2] = valueHi;
return target;
return addShort(target, 1, value);
}
private byte[] addShort(byte[] target, int position, int value) {
byte valueLo = (byte) (value & 0xff);
byte valueHi = (byte) (value >> 8);
target[position] = valueLo;
target[position+1] = valueHi;
return target;
}
}

View File

@ -2569,6 +2569,9 @@
<string name="pref_voice_detect_duration_5">5 seconds</string>
<string name="pref_voice_detect_duration_10">10 seconds</string>
<string name="pref_voice_detect_duration_15">15 seconds</string>
<string name="pref_voice_passthrough_enabled">Passthrough</string>
<string name="pref_voice_passthrough_enabled_summary">Allow external sounds to pass through to your ears</string>
<string name="pref_voice_passthrough_level">Passthrough level</string>
<string name="pref_header_heartrate_sleep">Sleep</string>
<string name="pref_header_heartrate_allday">All-day monitoring</string>
<string name="pref_header_heartrate_alerts">Heart rate alerts</string>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SwitchPreferenceCompat
android:defaultValue="false"
android:icon="@drawable/ic_surround"
android:key="active_noise_cancelling_toggle"
android:layout="@layout/preference_checkbox"
android:summary="@string/prefs_active_noise_cancelling_summary"
android:title="@string/prefs_active_noise_cancelling" />
</androidx.preference.PreferenceScreen>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SeekBarPreference
style="@style/Widget.AppCompat.SeekBar.Discrete"
android:icon="@drawable/ic_hearing"
android:key="bandw_pseries_gui_vpt_level"
android:max="2"
android:summary="@string/pref_voice_passthrough_enabled_summary"
android:title="@string/pref_voice_passthrough_level" />
</androidx.preference.PreferenceScreen>