refactor: add protocol implementation and command variables

This commit is contained in:
duhow
2026-06-14 10:34:05 +02:00
committed by José Rebelo
parent cb5c820f4f
commit 9fd323789e
7 changed files with 288 additions and 291 deletions
@@ -38,8 +38,4 @@ public abstract class AbstractSoundcoreProtocol extends GBDeviceProtocol {
else return 0x00;
}
public byte[] encodeDeviceInfoRequest() {
return new SoundcorePacket((short) 0x0101).encode();
}
}
@@ -10,20 +10,24 @@ import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.AbstractSoundcoreProtocol;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.SoundcorePacket;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.protocol.impl.v1.SoundcoreProtocolImplV1;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class SoundcoreAeroFitProtocol extends AbstractSoundcoreProtocol {
public class SoundcoreAeroFitProtocol extends SoundcoreProtocolImplV1 {
private static final Logger LOG = LoggerFactory.getLogger(SoundcoreAeroFitProtocol.class);
private static final short CMD_GET_CONNECTED_DEVICES = (short) 0x010b;
private static final short CMD_SET_BATTERY_LOW_TONE = (short) 0x8210;
private static final short CMD_SET_GAMING_MODE = (short) 0x8701;
private static final short CMD_SET_TOUCH_LOCK = (short) 0x9410;
private static final int battery_case = 0;
private static final int battery_earphone_left = 1;
private static final int battery_earphone_right = 2;
@@ -34,8 +38,7 @@ public class SoundcoreAeroFitProtocol extends AbstractSoundcoreProtocol {
@Override
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
ByteBuffer buf = ByteBuffer.wrap(responseData);
SoundcorePacket packet = SoundcorePacket.decode(buf);
SoundcorePacket packet = decodePacket(responseData);
if (packet == null)
return null;
@@ -44,21 +47,21 @@ public class SoundcoreAeroFitProtocol extends AbstractSoundcoreProtocol {
short cmd = packet.getCommand();
byte[] payload = packet.getPayload();
if (cmd == (short) 0x0101) {
if (cmd == CMD_GET_DEVICE_INFO) {
String firmware1 = readString(payload, 4, 5);
String firmware2 = readString(payload, 9, 5);
String serialNumber = readString(payload, 14, 16);
handleBatteryInfo(devEvts, null, payload[2], payload[3]);
devEvts.add(buildVersionInfo(firmware1, firmware2, serialNumber));
} else if (cmd == (short) 0x010b) {
} else if (cmd == CMD_GET_CONNECTED_DEVICES) {
// shows connected devices from 50 onwards
// readString(payload, 50, 112)
// maybe also other settings
LOG.debug("Incoming Information about connected devices, dump: " + hexdump(responseData));
} else if (cmd == (short) 0x0301) { // Battery Update
} else if (cmd == CMD_NOTIFY_BATTERY_INFO) {
handleBatteryInfo(devEvts, payload[2], payload[0], payload[1]);
} else if (cmd == (short) 0x0401) {
} else if (cmd == CMD_NOTIFY_CHARGING_INFO) {
boolean leftInCase = payload[0] == 0x01;
boolean rightInCase = payload[1] == 0x01;
LOG.info("Left Earbud in Charging Case: " + leftInCase + ", Right Earbud in Charging Case: " + rightInCase);
@@ -88,21 +91,21 @@ public class SoundcoreAeroFitProtocol extends AbstractSoundcoreProtocol {
// Control
case PREF_SOUNDCORE_CONTROL_TOUCH_DISABLED:
boolean touchDisabled = prefs.getBoolean(PREF_SOUNDCORE_CONTROL_TOUCH_DISABLED, false);
return new SoundcorePacket((short) 0x9410, new byte[]{encodeBoolean(touchDisabled)}).encode();
return encodeBooleanCommand(CMD_SET_TOUCH_LOCK, touchDisabled);
// Miscellaneous Settings
case PREF_SOUNDCORE_TOUCH_TONE:
boolean touchTone = prefs.getBoolean(PREF_SOUNDCORE_TOUCH_TONE, false);
return new SoundcorePacket((short) 0x8301, new byte[]{encodeBoolean(touchTone)}).encode();
return encodeBooleanCommand(CMD_SET_TOUCH_TONE, touchTone);
case PREF_SOUNDCORE_BATTERY_LOW_TONE:
boolean batteryLowTone = prefs.getBoolean(PREF_SOUNDCORE_BATTERY_LOW_TONE, false);
return new SoundcorePacket((short) 0x8210, new byte[]{encodeBoolean(batteryLowTone)}).encode();
return encodeBooleanCommand(CMD_SET_BATTERY_LOW_TONE, batteryLowTone);
case PREF_SOUNDCORE_GAMING_MODE:
boolean gamingMode = prefs.getBoolean(PREF_SOUNDCORE_GAMING_MODE, false);
return new SoundcorePacket((short) 0x8701, new byte[]{encodeBoolean(gamingMode)}).encode();
return encodeBooleanCommand(CMD_SET_GAMING_MODE, gamingMode);
case PREF_SOUNDCORE_AUTO_POWER_OFF:
int duration = Integer.parseInt(prefs.getString(PREF_SOUNDCORE_AUTO_POWER_OFF, "3"));
return setAutoPowerOff(duration).encode();
return encodeAutoPowerOff(duration, (byte) 0x03);
default:
LOG.debug("Unsupported CONFIG: " + config);
}
@@ -110,29 +113,4 @@ public class SoundcoreAeroFitProtocol extends AbstractSoundcoreProtocol {
return super.encodeSendConfiguration(config);
}
/**
* 0: No Auto Power off
* 1: Auto Power off 10 min
* 2: Auto Power off 20 min
* 3: Auto Power off 30 min
* 4: Auto Power off 60 min
*/
private SoundcorePacket setAutoPowerOff(int duration) {
byte[] payload;
if (duration > 0)
payload = new byte[] { (byte)0x01, (byte)(duration - 1) };
else
payload = new byte[] { (byte)0x00, (byte)0x03 };
return new SoundcorePacket((short) 0x8601, payload);
}
@Override
public byte[] encodeFindDevice(boolean start) {
boolean findLeft = start;
boolean findRight = start;
byte[] payload = new byte[]{encodeBoolean(findLeft), encodeBoolean(findRight), 0x00};
return new SoundcorePacket((short) 0x8910, payload).encode();
}
}
@@ -2,12 +2,9 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.liberty;
import static nodomain.freeyourgadget.gadgetbridge.util.GB.hexdump;
import android.content.SharedPreferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
@@ -15,14 +12,21 @@ import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSett
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.AbstractSoundcoreProtocol;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.SoundcorePacket;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.protocol.impl.v1.SoundcoreProtocolImplV1;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class SoundcoreLibertyProtocol extends AbstractSoundcoreProtocol {
public class SoundcoreLibertyProtocol extends SoundcoreProtocolImplV1 {
private static final Logger LOG = LoggerFactory.getLogger(SoundcoreLibertyProtocol.class);
private static final short CMD_GET_UNKNOWN_DATA_8D01 = (short) 0x8d01;
private static final short CMD_GET_UNKNOWN_DATA_8205 = (short) 0x8205;
private static final short CMD_SET_AMBIENT_SOUND_CONTROL_BUTTON_MODE = (short) 0x8206;
private static final short CMD_SET_TOUCH_LOCK = (short) 0x8304;
private static final short CMD_SET_WEARING_DETECTION = (short) 0x8101;
private static final short CMD_SET_WEARING_TONE = (short) 0x8c01;
private static final int battery_case = 0;
private static final int battery_earphone_left = 1;
private static final int battery_earphone_right = 2;
@@ -33,8 +37,7 @@ public class SoundcoreLibertyProtocol extends AbstractSoundcoreProtocol {
@Override
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
ByteBuffer buf = ByteBuffer.wrap(responseData);
SoundcorePacket packet = SoundcorePacket.decode(buf);
SoundcorePacket packet = decodePacket(responseData);
if (packet == null)
return null;
@@ -43,7 +46,7 @@ public class SoundcoreLibertyProtocol extends AbstractSoundcoreProtocol {
short cmd = packet.getCommand();
byte[] payload = packet.getPayload();
if (cmd == (short) 0x0101) {
if (cmd == CMD_GET_DEVICE_INFO) {
int batteryLeft = payload[2] * 20;
int batteryRight = payload[3] * 20;
@@ -55,15 +58,15 @@ public class SoundcoreLibertyProtocol extends AbstractSoundcoreProtocol {
devEvts.add(buildBatteryInfo(battery_earphone_left, batteryLeft));
devEvts.add(buildBatteryInfo(battery_earphone_right, batteryRight));
devEvts.add(buildVersionInfo(firmware1, firmware2, serialNumber));
} else if (cmd == (short) 0x8d01) {
} else if (cmd == CMD_GET_UNKNOWN_DATA_8D01) {
LOG.debug("Unknown incoming message - command: " + cmd + ", dump: " + hexdump(responseData));
} else if (cmd == (short) 0x8205) {
} else if (cmd == CMD_GET_UNKNOWN_DATA_8205) {
LOG.debug("Unknown incoming message - command: " + cmd + ", dump: " + hexdump(responseData));
} else if (cmd == (short) 0x0105) {
} else if (cmd == CMD_GET_UNKNOWN_DATA_0105) {
LOG.debug("Unknown incoming message - command: " + cmd + ", dump: " + hexdump(responseData));
} else if (cmd == (short) 0x0106) { // ANC Mode Update
decodeAudioMode(payload);
} else if (cmd == (short) 0x0301) { // Battery Update
} else if (cmd == CMD_NOTIFY_AUDIO_MODE) {
decodeAdvancedAudioMode(payload);
} else if (cmd == CMD_NOTIFY_BATTERY_INFO) {
int batteryLeft = payload[0] * 20;
int batteryRight = payload[1] * 20;
int batteryCase = payload[2] * 20;
@@ -79,40 +82,6 @@ public class SoundcoreLibertyProtocol extends AbstractSoundcoreProtocol {
return devEvts.toArray(new GBDeviceEvent[devEvts.size()]);
}
private void decodeAudioMode(byte[] payload) {
SharedPreferences prefs = getDevicePrefs().getPreferences();
SharedPreferences.Editor editor = prefs.edit();
String ambient_sound_mode = "off";
int anc_strength = 0;
if (payload[0] == 0x00) {
ambient_sound_mode = "noise_cancelling";
} else if (payload[0] == 0x01) {
ambient_sound_mode = "ambient_sound";
} else if (payload[0] == 0x02) {
ambient_sound_mode = "off";
}
if (payload[1] == 0x10) {
anc_strength = 0;
} else if (payload[1] == 0x20) {
anc_strength = 1;
} else if (payload[1] == 0x30) {
anc_strength = 2;
}
boolean vocal_mode = (payload[2] == 0x01);
boolean adaptive_anc = (payload[3] == 0x01);
boolean windnoiseReduction = (payload[4] == 0x01);
editor.putString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL, ambient_sound_mode);
editor.putInt(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL, anc_strength);
editor.putBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE, vocal_mode);
editor.putBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING, adaptive_anc);
editor.putBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WIND_NOISE_REDUCTION, windnoiseReduction);
editor.apply();
}
@Override
public byte[] encodeSendConfiguration(String config) {
Prefs prefs = getDevicePrefs();
@@ -125,7 +94,7 @@ public class SoundcoreLibertyProtocol extends AbstractSoundcoreProtocol {
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE:
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING:
case DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL:
return encodeAudioMode();
return encodeAdvancedAudioMode(true);
// Control
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_SINGLE_TAP_DISABLED:
@@ -178,13 +147,13 @@ public class SoundcoreLibertyProtocol extends AbstractSoundcoreProtocol {
// Miscellaneous Settings
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WEARING_DETECTION:
boolean wearingDetection = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WEARING_DETECTION, false);
return new SoundcorePacket((short) 0x8101, new byte[]{encodeBoolean(wearingDetection)}).encode();
return encodeBooleanCommand(CMD_SET_WEARING_DETECTION, wearingDetection);
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WEARING_TONE:
boolean wearingTone = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WEARING_TONE, false);
return new SoundcorePacket((short) 0x8c01, new byte[]{encodeBoolean(wearingTone)}).encode();
return encodeBooleanCommand(CMD_SET_WEARING_TONE, wearingTone);
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TOUCH_TONE:
boolean touchTone = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TOUCH_TONE, false);
return new SoundcorePacket((short) 0x8301, new byte[]{encodeBoolean(touchTone)}).encode();
return encodeBooleanCommand(CMD_SET_TOUCH_TONE, touchTone);
default:
LOG.debug("Unsupported CONFIG: " + config);
}
@@ -194,66 +163,14 @@ public class SoundcoreLibertyProtocol extends AbstractSoundcoreProtocol {
byte[] encodeMysteryDataRequest1() {
byte[] payload = new byte[]{0x00};
return new SoundcorePacket((short) 0x8d01, payload).encode();
return encodeCommand(CMD_GET_UNKNOWN_DATA_8D01, payload);
}
byte[] encodeMysteryDataRequest2() {
return new SoundcorePacket((short) 0x0105).encode();
return encodeRequest(CMD_GET_UNKNOWN_DATA_0105);
}
byte[] encodeMysteryDataRequest3() {
byte[] payload = new byte[]{0x00};
return new SoundcorePacket((short) 0x8205, payload).encode();
}
/**
* Encodes the following settings to a payload to set the audio-mode on the headphones:
* PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL If ANC, Transparent or neither should be active
* PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING If the strenght of the ANC should be set manual or adaptively according to ambient noise
* PREF_SONY_AMBIENT_SOUND_LEVEL How strong the ANC should be in manual mode
* PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE If the Transparency should focus on vocals or should be fully transparent
* PREF_SOUNDCORE_WIND_NOISE_REDUCTION If Transparency or ANC should reduce Wind Noise
* @return The payload
*/
private byte[] encodeAudioMode() {
Prefs prefs = getDevicePrefs();
byte ambient_sound_mode;
switch (prefs.getString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL, "off")) {
case "noise_cancelling":
ambient_sound_mode = 0x00;
break;
case "ambient_sound":
ambient_sound_mode = 0x01;
break;
case "off":
ambient_sound_mode = 0x02;
break;
default:
LOG.error("Invalid Ambient Mode selected");
return null;
}
byte anc_strength;
switch (prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL, 0)) {
case 0:
anc_strength = 0x10;
break;
case 1:
anc_strength = 0x20;
break;
case 2:
anc_strength = 0x30;
break;
default:
LOG.error("Invalid ANC Strength selected");
return null;
}
byte adaptive_anc = encodeBoolean(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING, true));
byte vocal_mode = encodeBoolean(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE, false));
byte windnoise_reduction = encodeBoolean(prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WIND_NOISE_REDUCTION, false));
byte[] payload = new byte[]{ambient_sound_mode, anc_strength, vocal_mode, adaptive_anc, windnoise_reduction, 0x01};
return new SoundcorePacket((short) 0x8106, payload).encode();
return encodeCommand(CMD_GET_UNKNOWN_DATA_8205, payload);
}
/**
@@ -280,7 +197,7 @@ public class SoundcoreLibertyProtocol extends AbstractSoundcoreProtocol {
return null;
}
payload = new byte[]{0x00, action.getCode(), enabled_byte};
return new SoundcorePacket((short) 0x8304, payload).encode();
return encodeCommand(CMD_SET_TOUCH_LOCK, payload);
}
/**
@@ -307,8 +224,7 @@ public class SoundcoreLibertyProtocol extends AbstractSoundcoreProtocol {
LOG.error("Invalid Tap action");
return null;
}
byte[] payload = new byte[] {encodeBoolean(right), action.getCode(), function_byte};
return new SoundcorePacket((short) 0x8104, payload).encode();
return encodeControlFunctionMessage(right, action.getCode(), function_byte);
}
/**
@@ -318,7 +234,7 @@ public class SoundcoreLibertyProtocol extends AbstractSoundcoreProtocol {
private byte[] encodeControlAmbientModeMessage(boolean anc, boolean transparency, boolean normal) {
// Original app does not allow only one true flag. Unsure if Earbuds accept this state.
byte ambientModes = (byte) (4 * (normal?1:0) + 2 * (transparency?1:0) + (anc?1:0));
return new SoundcorePacket((short) 0x8206, new byte[] {ambientModes}).encode();
return encodeCommand(CMD_SET_AMBIENT_SOUND_CONTROL_BUTTON_MODE, new byte[] {ambientModes});
}
}
@@ -38,12 +38,15 @@ import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.*;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.protocol.impl.v1.SoundcoreProtocolImplV1.CMD_GET_DEVICE_INFO;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.protocol.impl.v1.SoundcoreProtocolImplV1.CMD_NOTIFY_BATTERY_INFO;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.protocol.impl.v1.SoundcoreProtocolImplV1.CMD_NOTIFY_CHARGING_INFO;
import static nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.protocol.impl.v1.SoundcoreProtocolImplV1.CMD_SET_AUTO_POWER_OFF;
public class SoundcoreMotion300Protocol extends GBDeviceProtocol {
private static final Logger LOG = LoggerFactory.getLogger(SoundcoreMotion300Protocol.class);
// Some of these commands are not used right now, they serve as documentation
private static final short CMD_GET_DEVICE_INFO = (short)0x0101;
private static final short CMD_GET_LDAC_MODE = (short)0x7f01;
private static final short CMD_GET_BUTTON_BRIGHTNESS = (short)0x9310;
private static final short CMD_GET_EQUALIZER = (short)0x8902;
@@ -51,14 +54,11 @@ public class SoundcoreMotion300Protocol extends GBDeviceProtocol {
private static final short CMD_SET_VOICE_PROMPTS = (short)0x9001;
private static final short CMD_SET_BUTTON_BRIGHTNESS = (short)0x9210;
private static final short CMD_SET_AUTO_POWER_OFF = (short)0x8601;
private static final short CMD_SET_LDAC_MODE = (short)0xff01;
private static final short CMD_SET_ADAPTIVE_DIRECTION = (short)0x8a02;
private static final short CMD_SET_EQUALIZER_PRESET = (short)0x8b02;
private static final short CMD_SET_EQUALIZER_CUSTOM = (short)0x8d02;
private static final short CMD_NOTIFY_BATTERY_INFO = (short)0x0301;
private static final short CMD_NOTIFY_CHARGING_INFO = (short)0x0401;
private static final short CMD_NOTIFY_VOLUME_INFO = (short)0x0901;
private static final short CMD_NOTIFY_PLAYBACK_INFO = (short)0x2101;
private static final short CMD_NOTIFY_BASS_MODE = (short)0x8e02;
@@ -0,0 +1,206 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.protocol.impl.v1;
import android.content.SharedPreferences;
import java.nio.ByteBuffer;
import java.util.Arrays;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.AbstractSoundcoreProtocol;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.SoundcorePacket;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public abstract class SoundcoreProtocolImplV1 extends AbstractSoundcoreProtocol {
public static final short CMD_GET_DEVICE_INFO = (short) 0x0101;
public static final short CMD_GET_UNKNOWN_DATA_0105 = (short) 0x0105;
public static final short CMD_NOTIFY_BATTERY_INFO = (short) 0x0301;
public static final short CMD_NOTIFY_CHARGING_INFO = (short) 0x0401;
public static final short CMD_NOTIFY_AUDIO_MODE = (short) 0x0106;
public static final short CMD_SET_AUDIO_MODE = (short) 0x8106;
public static final short CMD_SET_CONTROL_FUNCTION = (short) 0x8104;
public static final short CMD_SET_TOUCH_TONE = (short) 0x8301;
public static final short CMD_SET_AUTO_POWER_OFF = (short) 0x8601;
public static final short CMD_SET_FIND_DEVICE = (short) 0x8910;
public static final short CMD_ENABLE_PAIRING_MODE = (short) 0x850b;
protected SoundcoreProtocolImplV1(final GBDevice device) {
super(device);
}
protected SoundcorePacket decodePacket(final byte[] responseData) {
return SoundcorePacket.decode(ByteBuffer.wrap(responseData));
}
protected byte[] encodeRequest(final short command) {
return new SoundcorePacket(command).encode();
}
protected byte[] encodeCommand(final short command, final byte[] payload) {
return new SoundcorePacket(command, payload).encode();
}
protected byte[] encodeBooleanCommand(final short command, final boolean enabled) {
return encodeCommand(command, new byte[]{encodeBoolean(enabled)});
}
public byte[] encodeDeviceInfoRequest() {
return encodeRequest(CMD_GET_DEVICE_INFO);
}
@Override
public byte[] encodeFindDevice(final boolean start) {
return encodeCommand(CMD_SET_FIND_DEVICE, new byte[]{
encodeBoolean(start),
encodeBoolean(start),
0x00
});
}
protected byte[] encodePairingMode() {
return encodeCommand(CMD_ENABLE_PAIRING_MODE, new byte[]{0x00, (byte) 0x90});
}
protected byte[] encodeAutoPowerOff(final int duration, final byte disabledDuration) {
final byte[] payload;
if (duration > 0) {
payload = new byte[]{0x01, (byte) (duration - 1)};
} else {
payload = new byte[]{0x00, disabledDuration};
}
return encodeCommand(CMD_SET_AUTO_POWER_OFF, payload);
}
protected byte[] encodeControlFunctionMessage(final boolean right, final byte action, final byte function) {
return encodeCommand(CMD_SET_CONTROL_FUNCTION, new byte[]{encodeBoolean(right), action, function});
}
protected byte[] encodeAdvancedAudioMode(final boolean appendStateByte) {
final Prefs prefs = getDevicePrefs();
final Byte ambientSoundMode = encodeAmbientSoundMode(prefs.getString(
DeviceSettingsPreferenceConst.PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL,
"off"
));
if (ambientSoundMode == null) {
return null;
}
final Byte ancStrength = encodeAncStrength(prefs.getInt(
DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL,
0
));
if (ancStrength == null) {
return null;
}
final byte vocalMode = encodeBoolean(prefs.getBoolean(
DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE,
false
));
final byte adaptiveAnc = encodeBoolean(prefs.getBoolean(
DeviceSettingsPreferenceConst.PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING,
true
));
final byte windNoiseReduction = encodeBoolean(prefs.getBoolean(
DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WIND_NOISE_REDUCTION,
false
));
final byte[] payload = new byte[]{
ambientSoundMode.byteValue(),
ancStrength.byteValue(),
vocalMode,
adaptiveAnc,
windNoiseReduction
};
if (appendStateByte) {
final byte[] statePayload = Arrays.copyOf(payload, payload.length + 1);
statePayload[statePayload.length - 1] = 0x01;
return encodeCommand(CMD_SET_AUDIO_MODE, statePayload);
}
return encodeCommand(CMD_SET_AUDIO_MODE, payload);
}
protected void decodeAdvancedAudioMode(final byte[] payload) {
if (payload.length < 5) {
return;
}
final SharedPreferences.Editor editor = getDevicePrefs().getPreferences().edit();
editor.putString(
DeviceSettingsPreferenceConst.PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL,
decodeAmbientSoundMode(payload[0])
);
editor.putInt(
DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL,
decodeAncStrength(payload[1])
);
editor.putBoolean(
DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE,
payload[2] == 0x01
);
editor.putBoolean(
DeviceSettingsPreferenceConst.PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING,
payload[3] == 0x01
);
editor.putBoolean(
DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WIND_NOISE_REDUCTION,
payload[4] == 0x01
);
editor.apply();
}
protected Byte encodeAmbientSoundMode(final String ambientMode) {
switch (ambientMode) {
case "noise_cancelling":
return (byte) 0x00;
case "ambient_sound":
return (byte) 0x01;
case "off":
return (byte) 0x02;
default:
return null;
}
}
protected String decodeAmbientSoundMode(final byte ambientMode) {
switch (ambientMode) {
case 0x00:
return "noise_cancelling";
case 0x01:
return "ambient_sound";
case 0x02:
default:
return "off";
}
}
protected Byte encodeAncStrength(final int strength) {
if (strength < 0 || strength > 2) {
return null;
}
return (byte) ((strength + 1) << 4);
}
protected int decodeAncStrength(final byte strength) {
switch (strength & 0x30) {
case 0x20:
return 1;
case 0x30:
return 2;
case 0x10:
default:
return 0;
}
}
}
@@ -7,7 +7,6 @@ import android.content.SharedPreferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -15,22 +14,23 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.AbstractSoundcoreProtocol;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.SoundcorePacket;
import nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.protocol.impl.v1.SoundcoreProtocolImplV1;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class SoundcoreQ30Protocol extends AbstractSoundcoreProtocol {
public class SoundcoreQ30Protocol extends SoundcoreProtocolImplV1 {
private static final Logger LOG = LoggerFactory.getLogger(SoundcoreQ30Protocol.class);
private static final short CMD_SET_EQUALIZER = (short) 0x8102;
protected SoundcoreQ30Protocol(GBDevice device) {
super(device);
}
@Override
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
ByteBuffer buf = ByteBuffer.wrap(responseData);
SoundcorePacket packet = SoundcorePacket.decode(buf);
SoundcorePacket packet = decodePacket(responseData);
if (packet == null)
return null;
@@ -39,7 +39,7 @@ public class SoundcoreQ30Protocol extends AbstractSoundcoreProtocol {
short cmd = packet.getCommand();
byte[] payload = packet.getPayload();
if (cmd == (short) 0x0101) {
if (cmd == CMD_GET_DEVICE_INFO) {
int battery = payload[0] * 20; // only 20% steps available here
devEvts.add(buildBatteryInfo(0, battery));
@@ -56,9 +56,9 @@ public class SoundcoreQ30Protocol extends AbstractSoundcoreProtocol {
String serialNumber = readString(payload, 44, 16);
devEvts.add(buildVersionInfo(firmware1, firmware2, serialNumber));
} else if (cmd == (short) 0x0106) { // ANC Mode Updated by Button
} else if (cmd == CMD_NOTIFY_AUDIO_MODE) {
decodeAudioMode(payload);
} else if (cmd == (short) 0x8106) {
} else if (cmd == CMD_SET_AUDIO_MODE) {
// Acknowledgement for changed Ambient Mode
// empty payload
} else {
@@ -94,18 +94,12 @@ public class SoundcoreQ30Protocol extends AbstractSoundcoreProtocol {
private byte[] encodeAudioMode() {
Prefs prefs = getDevicePrefs();
byte ambient_sound_mode;
switch (prefs.getString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL, "off")) {
case "noise_cancelling":
ambient_sound_mode = 0x00;
break;
case "ambient_sound":
ambient_sound_mode = 0x01;
break;
case "off":
ambient_sound_mode = 0x02;
break;
default:
final Byte ambient_sound_mode = encodeAmbientSoundMode(prefs.getString(
DeviceSettingsPreferenceConst.PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL,
"off"
));
if (ambient_sound_mode == null) {
LOG.error("Invalid Ambient Mode selected");
return null;
}
@@ -126,8 +120,8 @@ public class SoundcoreQ30Protocol extends AbstractSoundcoreProtocol {
return null;
}
byte[] payload = new byte[]{ambient_sound_mode, anc_mode, 0x01};
return new SoundcorePacket((short) 0x8106, payload).encode();
byte[] payload = new byte[]{ambient_sound_mode.byteValue(), anc_mode, 0x01};
return encodeCommand(CMD_SET_AUDIO_MODE, payload);
}
/**
@@ -136,17 +130,9 @@ public class SoundcoreQ30Protocol extends AbstractSoundcoreProtocol {
private void decodeAudioMode(byte[] payload) {
SharedPreferences prefs = getDevicePrefs().getPreferences();
SharedPreferences.Editor editor = prefs.edit();
String ambient_sound_mode = "off";
String ambient_sound_mode = decodeAmbientSoundMode(payload[0]);
String anc_mode = "transport";
if (payload[0] == 0x00) {
ambient_sound_mode = "noise_cancelling";
} else if (payload[0] == 0x01) {
ambient_sound_mode = "ambient_sound";
} else if (payload[0] == 0x02) {
ambient_sound_mode = "off";
}
if (payload[1] == 0x00) {
anc_mode = "transport";
} else if (payload[1] == 0x01) {
@@ -178,7 +164,7 @@ public class SoundcoreQ30Protocol extends AbstractSoundcoreProtocol {
byte band8 = 0x78;
byte[] payload = new byte[]{(byte) 0xfe, (byte) 0xfe, band1, band2, band3, band4, band5, band6, band7, band8};
return new SoundcorePacket((short) 0x8102, payload).encode();
return encodeCommand(CMD_SET_EQUALIZER, payload);
}
private void decodeEqualizer(byte[] payload) {
@@ -195,6 +181,6 @@ public class SoundcoreQ30Protocol extends AbstractSoundcoreProtocol {
byte[] encodeMysteryDataRequest2() {
return new SoundcorePacket((short) 0x0105).encode();
return encodeRequest(CMD_GET_UNKNOWN_DATA_0105);
}
}
@@ -2,7 +2,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.soundcore.sport_x20
import android.content.SharedPreferences;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
@@ -15,6 +14,8 @@ import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class SoundcoreSportX20Protocol extends SoundcoreLibertyProtocol {
private static final short CMD_SET_EQUALIZER = (short) 0x8703;
private static final short CMD_SET_3D_SURROUND = (short) 0x8602;
private static final int CUSTOM_PRESET_ID = 0xfe;
private static final int EQ_BANDS = 8;
@@ -53,11 +54,10 @@ public class SoundcoreSportX20Protocol extends SoundcoreLibertyProtocol {
@Override
public GBDeviceEvent[] decodeResponse(final byte[] responseData) {
final ByteBuffer buf = ByteBuffer.wrap(responseData);
final SoundcorePacket packet = SoundcorePacket.decode(buf);
final SoundcorePacket packet = decodePacket(responseData);
if (packet != null && packet.getCommand() == (short) 0x0106) {
decodeAncAudioMode(packet.getPayload());
if (packet != null && packet.getCommand() == CMD_NOTIFY_AUDIO_MODE) {
decodeAdvancedAudioMode(packet.getPayload());
return new GBDeviceEvent[0];
}
@@ -80,7 +80,7 @@ public class SoundcoreSportX20Protocol extends SoundcoreLibertyProtocol {
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE:
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING:
case DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL:
return encodeAncAudioMode();
return encodeAdvancedAudioMode(false);
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_SINGLE_TAP_ACTION_LEFT:
prefString = prefs.getString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_CONTROL_SINGLE_TAP_ACTION_LEFT, "PLAYPAUSE");
@@ -109,11 +109,11 @@ public class SoundcoreSportX20Protocol extends SoundcoreLibertyProtocol {
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TOUCH_TONE:
final boolean pressAlert = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TOUCH_TONE, false);
return new SoundcorePacket((short) 0x8301, new byte[]{encodeBoolean(pressAlert)}).encode();
return encodeBooleanCommand(CMD_SET_TOUCH_TONE, pressAlert);
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_3D_SURROUND:
final boolean surround3d = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_3D_SURROUND, false);
return new SoundcorePacket((short) 0x8602, new byte[]{encodeBoolean(surround3d)}).encode();
return encodeBooleanCommand(CMD_SET_3D_SURROUND, surround3d);
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_EQUALIZER_PRESET:
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_EQUALIZER_BAND1_VALUE:
@@ -127,21 +127,13 @@ public class SoundcoreSportX20Protocol extends SoundcoreLibertyProtocol {
return encodeEqualizer();
case DeviceSettingsPreferenceConst.PREF_SOUNDCORE_ENABLE_PAIRING_MODE:
return new SoundcorePacket((short) 0x850b, new byte[]{0x00, (byte) 0x90}).encode();
return encodePairingMode();
default:
return super.encodeSendConfiguration(config);
}
}
@Override
public byte[] encodeFindDevice(final boolean start) {
final boolean findLeft = start;
final boolean findRight = start;
final byte[] payload = new byte[]{encodeBoolean(findLeft), encodeBoolean(findRight), 0x00};
return new SoundcorePacket((short) 0x8910, payload).encode();
}
private byte[] encodeControlFunctionMessage(final TapAction action, final boolean right, final TapFunction function) {
final byte functionByte;
switch (action) {
@@ -158,8 +150,7 @@ public class SoundcoreSportX20Protocol extends SoundcoreLibertyProtocol {
return null;
}
final byte[] payload = new byte[]{encodeBoolean(right), action.getCode(), functionByte};
return new SoundcorePacket((short) 0x8104, payload).encode();
return encodeControlFunctionMessage(right, action.getCode(), functionByte);
}
/**
@@ -170,83 +161,7 @@ public class SoundcoreSportX20Protocol extends SoundcoreLibertyProtocol {
* 4: Auto Power off 60 min
*/
private byte[] encodeAutoPowerOff(final int duration) {
final byte[] payload;
if (duration > 0) {
payload = new byte[]{(byte) 0x01, (byte) (duration - 1)};
} else {
payload = new byte[]{(byte) 0x00, (byte) 0x03};
}
return new SoundcorePacket((short) 0x8601, payload).encode();
}
private void decodeAncAudioMode(final byte[] payload) {
if (payload.length < 5) {
return;
}
final SharedPreferences prefs = getDevicePrefs().getPreferences();
final SharedPreferences.Editor editor = prefs.edit();
String ambientSoundMode = "off";
if (payload[0] == 0x00) {
ambientSoundMode = "noise_cancelling";
} else if (payload[0] == 0x01) {
ambientSoundMode = "ambient_sound";
} else if (payload[0] == 0x02) {
ambientSoundMode = "off";
}
int ancStrength = ((payload[1] & 0x30) >> 4) - 1;
final boolean vocalMode = (payload[2] == 0x01);
final boolean adaptiveAnc = (payload[3] == 0x01);
final boolean windNoiseReduction = (payload[4] == 0x01);
editor.putString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL, ambientSoundMode);
editor.putInt(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL, ancStrength);
editor.putBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE, vocalMode);
editor.putBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING, adaptiveAnc);
editor.putBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WIND_NOISE_REDUCTION, windNoiseReduction);
editor.apply();
}
private byte[] encodeAncAudioMode() {
final Prefs prefs = getDevicePrefs();
final String ambientMode = prefs.getString(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_AMBIENT_SOUND_CONTROL, "off");
final int ancStrengthValue = prefs.getInt(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL, 0);
final boolean vocalMode = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_TRANSPARENCY_VOCAL_MODE, false);
final boolean adaptive = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_ADAPTIVE_NOISE_CANCELLING, true);
final boolean windReduction = prefs.getBoolean(DeviceSettingsPreferenceConst.PREF_SOUNDCORE_WIND_NOISE_REDUCTION, false);
if (ancStrengthValue < 0 || ancStrengthValue > 2) {
return null;
}
final byte ancStrengthByte = (byte) ((ancStrengthValue + 1) << 4);
final byte ambientModeByte;
switch (ambientMode) {
case "noise_cancelling":
ambientModeByte = 0x00;
break;
case "ambient_sound":
ambientModeByte = 0x01;
break;
case "off":
ambientModeByte = 0x02;
break;
default:
return null;
}
final byte adaptiveByte = encodeBoolean(adaptive);
final byte vocalModeByte = encodeBoolean(vocalMode);
final byte windReductionByte = encodeBoolean(windReduction);
final byte[] payload = new byte[]{ambientModeByte, ancStrengthByte, vocalModeByte, adaptiveByte, windReductionByte};
return new SoundcorePacket((short) 0x8106, payload).encode();
return encodeAutoPowerOff(duration, (byte) 0x03);
}
private byte[] encodeEqualizer() {
@@ -256,7 +171,7 @@ public class SoundcoreSportX20Protocol extends SoundcoreLibertyProtocol {
if (preset != CUSTOM_PRESET_ID) {
final byte[] presetPayload = EQ_PRESET_PAYLOADS.get(preset);
if (presetPayload != null) {
return new SoundcorePacket(CMD_SET_EQUALIZER, presetPayload).encode();
return encodeCommand(CMD_SET_EQUALIZER, presetPayload);
}
}
@@ -276,7 +191,7 @@ public class SoundcoreSportX20Protocol extends SoundcoreLibertyProtocol {
bandsToHex(tailBands)
);
return new SoundcorePacket(CMD_SET_EQUALIZER, payload).encode();
return encodeCommand(CMD_SET_EQUALIZER, payload);
}
private void decodeEqualizer(final byte[] payload) {