mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-03 17:02:13 +01:00
Sony Headphones: Merge V3 protocol into V2
The protocols are not actually different - the different messages only depended on whether the devices supports wind noise reduction or not.
This commit is contained in:
parent
f91156cd3e
commit
0301d0e9a8
@ -16,8 +16,6 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
@ -25,9 +23,7 @@ import java.util.regex.Pattern;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCapabilities;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
|
||||
|
||||
public class SonyWF1000XM4Coordinator extends SonyHeadphonesCoordinator {
|
||||
@Override
|
||||
|
@ -54,7 +54,6 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.prot
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.AbstractSonyProtocolImpl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.SonyProtocolImplV1;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v2.SonyProtocolImplV2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v3.SonyProtocolImplV3;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
|
||||
|
||||
public class SonyHeadphonesProtocol extends GBDeviceProtocol {
|
||||
@ -123,21 +122,11 @@ public class SonyHeadphonesProtocol extends GBDeviceProtocol {
|
||||
// Wi-SP600N: 01:00:40:10
|
||||
protocolVersion = "v1";
|
||||
} else if (message.getPayload().length == 8) {
|
||||
switch (message.getPayload()[2]) {
|
||||
case 0x01:
|
||||
// WF-1000XM4 1.1.5: 01:00:01:00:00:00:00:00
|
||||
protocolVersion = "v2";
|
||||
break;
|
||||
case 0x03:
|
||||
// LinkBuds S 2.0.2: 01:00:03:00:00:07:00:00
|
||||
// WH-1000XM5 1.1.3: 01:00:03:00:00:00:00:00
|
||||
// WF-1000XM5 2.0.1: 01:00:03:00:10:04:00:00
|
||||
protocolVersion = "v3";
|
||||
break;
|
||||
default:
|
||||
LOG.error("Unexpected version for payload of length 8: {}", message.getPayload()[2]);
|
||||
return events.toArray(new GBDeviceEvent[0]);
|
||||
}
|
||||
// WF-1000XM4 1.1.5: 01:00:01:00:00:00:00:00
|
||||
// LinkBuds S 2.0.2: 01:00:03:00:00:07:00:00
|
||||
// WH-1000XM5 1.1.3: 01:00:03:00:00:00:00:00
|
||||
// WF-1000XM5 2.0.1: 01:00:03:00:10:04:00:00
|
||||
protocolVersion = "v3";
|
||||
} else {
|
||||
LOG.error("Unexpected init response payload length: {}", message.getPayload().length);
|
||||
return events.toArray(new GBDeviceEvent[0]);
|
||||
@ -160,9 +149,6 @@ public class SonyHeadphonesProtocol extends GBDeviceProtocol {
|
||||
case "v2":
|
||||
protocolImpl = new SonyProtocolImplV2(getDevice());
|
||||
break;
|
||||
case "v3":
|
||||
protocolImpl = new SonyProtocolImplV3(getDevice());
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unknown protocol version {}", protocolVersion);
|
||||
return events.toArray(new GBDeviceEvent[0]);
|
||||
|
@ -33,6 +33,11 @@ public enum PayloadTypeV2 {
|
||||
AUTOMATIC_POWER_OFF_SET(MessageType.COMMAND_1, 0x28),
|
||||
AUTOMATIC_POWER_OFF_NOTIFY(MessageType.COMMAND_1, 0x29),
|
||||
|
||||
AMBIENT_SOUND_CONTROL_BUTTON_MODE_GET(MessageType.COMMAND_1, 0xfa),
|
||||
AMBIENT_SOUND_CONTROL_BUTTON_MODE_RET(MessageType.COMMAND_1, 0xfb),
|
||||
AMBIENT_SOUND_CONTROL_BUTTON_MODE_SET(MessageType.COMMAND_1, 0xfc),
|
||||
AMBIENT_SOUND_CONTROL_BUTTON_MODE_NOTIFY(MessageType.COMMAND_1, 0xfd),
|
||||
|
||||
UNKNOWN(MessageType.UNKNOWN, 0xff);
|
||||
|
||||
private final MessageType messageType;
|
||||
|
@ -66,17 +66,17 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
PayloadTypeV1.AMBIENT_SOUND_CONTROL_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AMBIENT_SOUND_CONTROL_GET.getCode(),
|
||||
(byte) 0x15
|
||||
(byte) (supportsWindNoiseCancelling() ? 0x17 : 0x15)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setAmbientSoundControl(final AmbientSoundControl ambientSoundControl) {
|
||||
final ByteBuffer buf = ByteBuffer.allocate(8);
|
||||
final ByteBuffer buf = ByteBuffer.allocate(supportsWindNoiseCancelling() ? 8 : 7);
|
||||
|
||||
buf.put(PayloadTypeV1.AMBIENT_SOUND_CONTROL_SET.getCode());
|
||||
buf.put((byte) 0x15);
|
||||
buf.put((byte) (supportsWindNoiseCancelling() ? 0x17 : 0x15));
|
||||
buf.put((byte) 0x01); // 0x00 while dragging the slider?
|
||||
|
||||
if (AmbientSoundControl.Mode.OFF.equals(ambientSoundControl.getMode())) {
|
||||
@ -91,10 +91,12 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
buf.put((byte) 0x00);
|
||||
}
|
||||
|
||||
if (AmbientSoundControl.Mode.WIND_NOISE_REDUCTION.equals(ambientSoundControl.getMode())) {
|
||||
buf.put((byte) 0x03);
|
||||
} else {
|
||||
buf.put((byte) 0x02);
|
||||
if (supportsWindNoiseCancelling()) {
|
||||
if (AmbientSoundControl.Mode.WIND_NOISE_REDUCTION.equals(ambientSoundControl.getMode())) {
|
||||
buf.put((byte) 0x03);
|
||||
} else {
|
||||
buf.put((byte) 0x02);
|
||||
}
|
||||
}
|
||||
|
||||
buf.put((byte) (ambientSoundControl.isFocusOnVoice() ? 0x01 : 0x00));
|
||||
@ -105,26 +107,50 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
|
||||
@Override
|
||||
public Request setSpeakToChatEnabled(SpeakToChatEnabled config) {
|
||||
LOG.warn("Speak-to-chat not implemented for V2");
|
||||
return null;
|
||||
return new Request(
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
|
||||
(byte) 0x0c,
|
||||
(byte) (config.isEnabled() ? 0x00 : 0x01), // TODO it's reversed?
|
||||
(byte) 0x01
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getSpeakToChatEnabled() {
|
||||
LOG.warn("Speak-to-chat not implemented for V2");
|
||||
return null;
|
||||
return new Request(
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
|
||||
(byte) 0x0c
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setSpeakToChatConfig(SpeakToChatConfig config) {
|
||||
LOG.warn("Speak-to-chat not implemented for V2");
|
||||
return null;
|
||||
return new Request(
|
||||
PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_SET.getCode(),
|
||||
(byte) 0x0c,
|
||||
config.getSensitivity().getCode(),
|
||||
config.getTimeout().getCode()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getSpeakToChatConfig() {
|
||||
LOG.warn("Speak-to-chat not implemented for V2");
|
||||
return null;
|
||||
return new Request(
|
||||
PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_GET.getCode(),
|
||||
(byte) 0x0c
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -234,26 +260,54 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
|
||||
@Override
|
||||
public Request getQuickAccess() {
|
||||
LOG.warn("Quick access not implemented for V2");
|
||||
return null;
|
||||
return new Request(
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
|
||||
(byte) 0x0d
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setQuickAccess(final QuickAccess quickAccess) {
|
||||
LOG.warn("Quick access not implemented for V2");
|
||||
return null;
|
||||
return new Request(
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
|
||||
(byte) 0x0d,
|
||||
(byte) 0x02,
|
||||
quickAccess.getModeDoubleTap().getCode(),
|
||||
quickAccess.getModeTripleTap().getCode()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getAmbientSoundControlButtonMode() {
|
||||
LOG.warn("Ambient sound control button modes not implemented for V2");
|
||||
return null;
|
||||
return new Request(
|
||||
PayloadTypeV2.AMBIENT_SOUND_CONTROL_BUTTON_MODE_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV2.AMBIENT_SOUND_CONTROL_BUTTON_MODE_GET.getCode(),
|
||||
(byte) 0x03
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setAmbientSoundControlButtonMode(final AmbientSoundControlButtonMode ambientSoundControlButtonMode) {
|
||||
LOG.warn("Ambient sound control button modes not implemented for V2");
|
||||
return null;
|
||||
return new Request(
|
||||
PayloadTypeV2.AMBIENT_SOUND_CONTROL_BUTTON_MODE_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV2.AMBIENT_SOUND_CONTROL_BUTTON_MODE_SET.getCode(),
|
||||
(byte) 0x03,
|
||||
(byte) 0x01,
|
||||
(byte) 0x35,
|
||||
(byte) 0x01,
|
||||
(byte) 0x00,
|
||||
ambientSoundControlButtonMode.getCode()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -305,8 +359,22 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
|
||||
@Override
|
||||
public Request setEqualizerCustomBands(final EqualizerCustomBands config) {
|
||||
LOG.warn("Equalizer custom bands not implemented for V2");
|
||||
return null;
|
||||
final ByteBuffer buf = ByteBuffer.allocate(10);
|
||||
|
||||
buf.put(PayloadTypeV1.EQUALIZER_SET.getCode());
|
||||
buf.put((byte) 0x00);
|
||||
buf.put((byte) 0xa0);
|
||||
buf.put((byte) 0x06);
|
||||
|
||||
buf.put((byte) (config.getBass() + 10));
|
||||
for (final Integer band : config.getBands()) {
|
||||
buf.put((byte) (band + 10));
|
||||
}
|
||||
|
||||
return new Request(
|
||||
PayloadTypeV1.EQUALIZER_SET.getMessageType(),
|
||||
buf.array()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -347,14 +415,25 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
|
||||
@Override
|
||||
public Request getVoiceNotifications() {
|
||||
LOG.warn("Voice notifications not implemented for V2");
|
||||
return null;
|
||||
return new Request(
|
||||
PayloadTypeV1.VOICE_NOTIFICATIONS_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.VOICE_NOTIFICATIONS_GET.getCode(),
|
||||
(byte) 0x01
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setVoiceNotifications(final VoiceNotifications config) {
|
||||
LOG.warn("Voice notifications not implemented for V2");
|
||||
return null;
|
||||
return new Request(
|
||||
PayloadTypeV1.VOICE_NOTIFICATIONS_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.VOICE_NOTIFICATIONS_SET.getCode(),
|
||||
(byte) 0x01,
|
||||
(byte) (config.isEnabled() ? 0x00 : 0x01) // reversed?
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -401,6 +480,9 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
case AUTOMATIC_POWER_OFF_RET:
|
||||
case AUTOMATIC_POWER_OFF_NOTIFY:
|
||||
return handleAutomaticPowerOff(payload);
|
||||
case AMBIENT_SOUND_CONTROL_BUTTON_MODE_RET:
|
||||
case AMBIENT_SOUND_CONTROL_BUTTON_MODE_NOTIFY:
|
||||
return handleAmbientSoundControlButtonMode(payload);
|
||||
}
|
||||
|
||||
return super.handlePayload(messageType, payload);
|
||||
@ -413,16 +495,18 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleAmbientSoundControl(final byte[] payload) {
|
||||
if (payload.length != 8) {
|
||||
if (payload.length != 8 && payload.length != 7) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x15) {
|
||||
if (payload[1] != 0x15 && payload[1] != 0x17) {
|
||||
LOG.warn("Not ambient sound control, ignoring {}", payload[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final boolean includesWindNoiseReduction = payload[1] == 0x17;
|
||||
|
||||
AmbientSoundControl.Mode mode = null;
|
||||
|
||||
if (payload[3] == (byte) 0x00) {
|
||||
@ -430,9 +514,17 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
} else if (payload[3] == (byte) 0x01) {
|
||||
// Enabled, determine mode
|
||||
|
||||
if (payload[5] == 0x03 || payload[5] == 0x05) {
|
||||
mode = AmbientSoundControl.Mode.WIND_NOISE_REDUCTION;
|
||||
} else if (payload[5] == 0x02) {
|
||||
if (includesWindNoiseReduction) {
|
||||
if (payload[5] == 0x03 || payload[5] == 0x05) {
|
||||
mode = AmbientSoundControl.Mode.WIND_NOISE_REDUCTION;
|
||||
} else if (payload[5] == 0x02) {
|
||||
if (payload[4] == (byte) 0x00) {
|
||||
mode = AmbientSoundControl.Mode.NOISE_CANCELLING;
|
||||
} else if (payload[4] == (byte) 0x01) {
|
||||
mode = AmbientSoundControl.Mode.AMBIENT_SOUND;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (payload[4] == (byte) 0x00) {
|
||||
mode = AmbientSoundControl.Mode.NOISE_CANCELLING;
|
||||
} else if (payload[4] == (byte) 0x01) {
|
||||
@ -446,15 +538,17 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Boolean focusOnVoice = booleanFromByte(payload[6]);
|
||||
int i = includesWindNoiseReduction ? 6 : 5;
|
||||
final Boolean focusOnVoice = booleanFromByte(payload[i]);
|
||||
if (focusOnVoice == null) {
|
||||
LOG.warn("Unknown focus on voice mode {}", String.format("%02x", payload[6]));
|
||||
LOG.warn("Unknown focus on voice mode {}", String.format("%02x", payload[i]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
int ambientSound = payload[7];
|
||||
i++;
|
||||
int ambientSound = payload[i];
|
||||
if (ambientSound < 0 || ambientSound > 20) {
|
||||
LOG.warn("Ambient sound level {} is out of range", String.format("%02x", payload[7]));
|
||||
LOG.warn("Ambient sound level {} is out of range", String.format("%02x", payload[i]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@ -533,6 +627,86 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleSpeakToChatEnabled(final byte[] payload) {
|
||||
if (payload.length != 4) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x0c) {
|
||||
LOG.warn("Not speak to chat enabled, ignoring");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Boolean disabled = booleanFromByte(payload[2]);
|
||||
if (disabled == null) {
|
||||
LOG.warn("Unknown speak to chat enabled code {}", String.format("%02x", payload[2]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Speak to chat: {}", !disabled);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(new SpeakToChatEnabled(!disabled).toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleQuickAccess(final byte[] payload) {
|
||||
if (payload.length != 5) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x0d || payload[2] != 0x02) {
|
||||
LOG.warn("Unexpected quick access payload bytes {}", String.format("%02x %02x", payload[1], payload[2]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final QuickAccess.Mode modeDouble = QuickAccess.Mode.fromCode(payload[3]);
|
||||
final QuickAccess.Mode modeTriple = QuickAccess.Mode.fromCode(payload[4]);
|
||||
if (modeDouble == null || modeTriple == null) {
|
||||
LOG.warn("Unknown quick access codes {}", String.format("%02x %02x", payload[3], payload[4]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Quick Access: Double Tap: {}, Triple Tap: {}", modeDouble, modeTriple);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(new QuickAccess(modeDouble, modeTriple).toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleAmbientSoundControlButtonMode(final byte[] payload) {
|
||||
if (payload.length != 7) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x03 || payload[2] != 0x01 || payload[3] != 0x35 || payload[4] != 0x01 || payload[5] != 0x00) {
|
||||
LOG.warn(
|
||||
"Unexpected ambient sound control button mode payload bytes {}",
|
||||
String.format("%02x %02x %02x %02x %02x", payload[1], payload[2], payload[3], payload[4], payload[5])
|
||||
);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final AmbientSoundControlButtonMode mode = AmbientSoundControlButtonMode.fromCode(payload[6]);
|
||||
if (mode == null) {
|
||||
LOG.warn("Unknown ambient sound control button mode code {}", String.format("%02x", payload[6]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Ambient Sound Control Buton Mode: {}", mode);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(mode.toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleButtonModes(final byte[] payload) {
|
||||
if (payload.length != 5) {
|
||||
@ -641,6 +815,10 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
return handlePauseWhenTakenOff(payload);
|
||||
case 0x03:
|
||||
return handleButtonModes(payload);
|
||||
case 0x0c:
|
||||
return handleSpeakToChatEnabled(payload);
|
||||
case 0x0d:
|
||||
return handleQuickAccess(payload);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
@ -672,8 +850,32 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleVoiceNotifications(final byte[] payload) {
|
||||
LOG.warn("Voice notifications not implemented for V2");
|
||||
return Collections.emptyList();
|
||||
if (payload.length != 4) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
boolean enabled;
|
||||
|
||||
// reversed?
|
||||
switch (payload[2]) {
|
||||
case 0x00:
|
||||
enabled = true;
|
||||
break;
|
||||
case 0x01:
|
||||
enabled = false;
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unknown voice notifications code {}", String.format("%02x", payload[3]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Voice Notifications: {}", enabled);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(new VoiceNotifications(enabled).toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -703,4 +905,37 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
|
||||
|
||||
throw new IllegalArgumentException("Unknown battery type " + batteryType);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ButtonModes.Mode decodeButtonMode(final byte b) {
|
||||
switch (b) {
|
||||
case (byte) 0xff:
|
||||
return ButtonModes.Mode.OFF;
|
||||
case (byte) 0x00:
|
||||
case (byte) 0x35: // Seems to be the only one that differs?
|
||||
return ButtonModes.Mode.AMBIENT_SOUND_CONTROL;
|
||||
case (byte) 0x20:
|
||||
return ButtonModes.Mode.PLAYBACK_CONTROL;
|
||||
case (byte) 0x10:
|
||||
return ButtonModes.Mode.VOLUME_CONTROL;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte encodeButtonMode(final ButtonModes.Mode buttonMode) {
|
||||
switch (buttonMode) {
|
||||
case OFF:
|
||||
return (byte) 0xff;
|
||||
case AMBIENT_SOUND_CONTROL:
|
||||
return (byte) (supportsWindNoiseCancelling() ? 0x35 : 0x00); // Seems to be the only one that differs?
|
||||
case PLAYBACK_CONTROL:
|
||||
return (byte) 0x20;
|
||||
case VOLUME_CONTROL:
|
||||
return (byte) 0x10;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown button mode " + buttonMode);
|
||||
}
|
||||
}
|
||||
|
@ -1,54 +0,0 @@
|
||||
/* Copyright (C) 2022-2024 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v3;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
|
||||
|
||||
public enum PayloadTypeV3 {
|
||||
AMBIENT_SOUND_CONTROL_BUTTON_MODE_GET(MessageType.COMMAND_1, 0xfa),
|
||||
AMBIENT_SOUND_CONTROL_BUTTON_MODE_RET(MessageType.COMMAND_1, 0xfb),
|
||||
AMBIENT_SOUND_CONTROL_BUTTON_MODE_SET(MessageType.COMMAND_1, 0xfc),
|
||||
AMBIENT_SOUND_CONTROL_BUTTON_MODE_NOTIFY(MessageType.COMMAND_1, 0xfd),
|
||||
|
||||
UNKNOWN(MessageType.UNKNOWN, 0xff);
|
||||
|
||||
private final MessageType messageType;
|
||||
private final byte code;
|
||||
|
||||
PayloadTypeV3(final MessageType messageType, final int code) {
|
||||
this.messageType = messageType;
|
||||
this.code = (byte) code;
|
||||
}
|
||||
|
||||
public MessageType getMessageType() {
|
||||
return this.messageType;
|
||||
}
|
||||
|
||||
public byte getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
public static PayloadTypeV3 fromCode(final MessageType messageType, final byte code) {
|
||||
for (final PayloadTypeV3 payloadType : values()) {
|
||||
if (messageType.equals(payloadType.messageType) && payloadType.code == code) {
|
||||
return payloadType;
|
||||
}
|
||||
}
|
||||
|
||||
return PayloadTypeV3.UNKNOWN;
|
||||
}
|
||||
}
|
@ -1,447 +0,0 @@
|
||||
/* Copyright (C) 2022-2024 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v3;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.ButtonModes;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerCustomBands;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatEnabled;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.PayloadTypeV1;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v2.SonyProtocolImplV2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
|
||||
public class SonyProtocolImplV3 extends SonyProtocolImplV2 {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SonyProtocolImplV3.class);
|
||||
|
||||
public SonyProtocolImplV3(final GBDevice device) {
|
||||
super(device);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getAmbientSoundControl() {
|
||||
return new Request(
|
||||
PayloadTypeV1.AMBIENT_SOUND_CONTROL_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AMBIENT_SOUND_CONTROL_GET.getCode(),
|
||||
(byte) 0x17
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setAmbientSoundControl(final AmbientSoundControl ambientSoundControl) {
|
||||
final ByteBuffer buf = ByteBuffer.allocate(7);
|
||||
|
||||
buf.put(PayloadTypeV1.AMBIENT_SOUND_CONTROL_SET.getCode());
|
||||
buf.put((byte) 0x17);
|
||||
buf.put((byte) 0x01); // 0x00 while dragging the slider?
|
||||
|
||||
if (AmbientSoundControl.Mode.OFF.equals(ambientSoundControl.getMode())) {
|
||||
buf.put((byte) 0x00);
|
||||
} else {
|
||||
buf.put((byte) 0x01);
|
||||
}
|
||||
|
||||
if (AmbientSoundControl.Mode.AMBIENT_SOUND.equals(ambientSoundControl.getMode())) {
|
||||
buf.put((byte) 0x01);
|
||||
} else {
|
||||
buf.put((byte) 0x00);
|
||||
}
|
||||
|
||||
buf.put((byte) (ambientSoundControl.isFocusOnVoice() ? 0x01 : 0x00));
|
||||
buf.put((byte) (ambientSoundControl.getAmbientSound()));
|
||||
|
||||
return new Request(PayloadTypeV1.AMBIENT_SOUND_CONTROL_SET.getMessageType(), buf.array());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setSpeakToChatEnabled(final SpeakToChatEnabled config) {
|
||||
return new Request(
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
|
||||
(byte) 0x0c,
|
||||
(byte) (config.isEnabled() ? 0x00 : 0x01), // TODO it's reversed?
|
||||
(byte) 0x01
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getSpeakToChatEnabled() {
|
||||
return new Request(
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
|
||||
(byte) 0x0c
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setSpeakToChatConfig(final SpeakToChatConfig config) {
|
||||
return new Request(
|
||||
PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_SET.getCode(),
|
||||
(byte) 0x0c,
|
||||
config.getSensitivity().getCode(),
|
||||
config.getTimeout().getCode()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getSpeakToChatConfig() {
|
||||
return new Request(
|
||||
PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.SPEAK_TO_CHAT_CONFIG_GET.getCode(),
|
||||
(byte) 0x0c
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getQuickAccess() {
|
||||
return new Request(
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
|
||||
(byte) 0x0d
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setQuickAccess(final QuickAccess quickAccess) {
|
||||
return new Request(
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
|
||||
(byte) 0x0d,
|
||||
(byte) 0x02,
|
||||
quickAccess.getModeDoubleTap().getCode(),
|
||||
quickAccess.getModeTripleTap().getCode()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getAmbientSoundControlButtonMode() {
|
||||
return new Request(
|
||||
PayloadTypeV3.AMBIENT_SOUND_CONTROL_BUTTON_MODE_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV3.AMBIENT_SOUND_CONTROL_BUTTON_MODE_GET.getCode(),
|
||||
(byte) 0x03
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setAmbientSoundControlButtonMode(final AmbientSoundControlButtonMode ambientSoundControlButtonMode) {
|
||||
return new Request(
|
||||
PayloadTypeV3.AMBIENT_SOUND_CONTROL_BUTTON_MODE_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV3.AMBIENT_SOUND_CONTROL_BUTTON_MODE_SET.getCode(),
|
||||
(byte) 0x03,
|
||||
(byte) 0x01,
|
||||
(byte) 0x35,
|
||||
(byte) 0x01,
|
||||
(byte) 0x00,
|
||||
ambientSoundControlButtonMode.getCode()
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setEqualizerCustomBands(final EqualizerCustomBands config) {
|
||||
final ByteBuffer buf = ByteBuffer.allocate(10);
|
||||
|
||||
buf.put(PayloadTypeV1.EQUALIZER_SET.getCode());
|
||||
buf.put((byte) 0x00);
|
||||
buf.put((byte) 0xa0);
|
||||
buf.put((byte) 0x06);
|
||||
|
||||
buf.put((byte) (config.getBass() + 10));
|
||||
for (final Integer band : config.getBands()) {
|
||||
buf.put((byte) (band + 10));
|
||||
}
|
||||
|
||||
return new Request(
|
||||
PayloadTypeV1.EQUALIZER_SET.getMessageType(),
|
||||
buf.array()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request getVoiceNotifications() {
|
||||
return new Request(
|
||||
PayloadTypeV1.VOICE_NOTIFICATIONS_GET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.VOICE_NOTIFICATIONS_GET.getCode(),
|
||||
(byte) 0x01
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Request setVoiceNotifications(final VoiceNotifications config) {
|
||||
return new Request(
|
||||
PayloadTypeV1.VOICE_NOTIFICATIONS_SET.getMessageType(),
|
||||
new byte[]{
|
||||
PayloadTypeV1.VOICE_NOTIFICATIONS_SET.getCode(),
|
||||
(byte) 0x01,
|
||||
(byte) (config.isEnabled() ? 0x00 : 0x01) // reversed?
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handlePayload(final MessageType messageType, final byte[] payload) {
|
||||
final PayloadTypeV3 payloadType = PayloadTypeV3.fromCode(messageType, payload[0]);
|
||||
|
||||
switch (payloadType) {
|
||||
case AMBIENT_SOUND_CONTROL_BUTTON_MODE_RET:
|
||||
case AMBIENT_SOUND_CONTROL_BUTTON_MODE_NOTIFY:
|
||||
return handleAmbientSoundControlButtonMode(payload);
|
||||
}
|
||||
|
||||
return super.handlePayload(messageType, payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleAmbientSoundControl(final byte[] payload) {
|
||||
if (payload.length != 7) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x17) {
|
||||
LOG.warn("Not ambient sound control, ignoring {}", payload[1]);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
AmbientSoundControl.Mode mode = null;
|
||||
|
||||
if (payload[3] == (byte) 0x00) {
|
||||
mode = AmbientSoundControl.Mode.OFF;
|
||||
} else if (payload[3] == (byte) 0x01) {
|
||||
// Enabled, determine mode
|
||||
|
||||
if (payload[4] == (byte) 0x00) {
|
||||
mode = AmbientSoundControl.Mode.NOISE_CANCELLING;
|
||||
} else if (payload[4] == (byte) 0x01) {
|
||||
mode = AmbientSoundControl.Mode.AMBIENT_SOUND;
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == null) {
|
||||
LOG.warn("Unable to determine ambient sound control mode from {}", GB.hexdump(payload));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Boolean focusOnVoice = booleanFromByte(payload[5]);
|
||||
if (focusOnVoice == null) {
|
||||
LOG.warn("Unknown focus on voice mode {}", String.format("%02x", payload[5]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
int ambientSound = payload[6];
|
||||
if (ambientSound < 0 || ambientSound > 20) {
|
||||
LOG.warn("Ambient sound level {} is out of range", String.format("%02x", payload[6]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final AmbientSoundControl ambientSoundControl = new AmbientSoundControl(mode, focusOnVoice, ambientSound);
|
||||
|
||||
LOG.debug("Ambient sound control: {}", ambientSoundControl);
|
||||
|
||||
final GBDeviceEventUpdatePreferences eventUpdatePreferences = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(ambientSoundControl.toPreferences());
|
||||
|
||||
return Collections.singletonList(eventUpdatePreferences);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleQuickAccess(final byte[] payload) {
|
||||
if (payload.length != 5) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x0d || payload[2] != 0x02) {
|
||||
LOG.warn("Unexpected quick access payload bytes {}", String.format("%02x %02x", payload[1], payload[2]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final QuickAccess.Mode modeDouble = QuickAccess.Mode.fromCode(payload[3]);
|
||||
final QuickAccess.Mode modeTriple = QuickAccess.Mode.fromCode(payload[4]);
|
||||
if (modeDouble == null || modeTriple == null) {
|
||||
LOG.warn("Unknown quick access codes {}", String.format("%02x %02x", payload[3], payload[4]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Quick Access: Double Tap: {}, Triple Tap: {}", modeDouble, modeTriple);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(new QuickAccess(modeDouble, modeTriple).toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleAmbientSoundControlButtonMode(final byte[] payload) {
|
||||
if (payload.length != 7) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x03 || payload[2] != 0x01 || payload[3] != 0x35 || payload[4] != 0x01 || payload[5] != 0x00) {
|
||||
LOG.warn(
|
||||
"Unexpected ambient sound control button mode payload bytes {}",
|
||||
String.format("%02x %02x %02x %02x %02x", payload[1], payload[2], payload[3], payload[4], payload[5])
|
||||
);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final AmbientSoundControlButtonMode mode = AmbientSoundControlButtonMode.fromCode(payload[6]);
|
||||
if (mode == null) {
|
||||
LOG.warn("Unknown ambient sound control button mode code {}", String.format("%02x", payload[6]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Ambient Sound Control Buton Mode: {}", mode);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(mode.toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<? extends GBDeviceEvent> handleSpeakToChatEnabled(final byte[] payload) {
|
||||
if (payload.length != 4) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
if (payload[1] != 0x0c) {
|
||||
LOG.warn("Not speak to chat enabled, ignoring");
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Boolean disabled = booleanFromByte(payload[2]);
|
||||
if (disabled == null) {
|
||||
LOG.warn("Unknown speak to chat enabled code {}", String.format("%02x", payload[2]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Speak to chat: {}", !disabled);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(new SpeakToChatEnabled(!disabled).toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleAutomaticPowerOffButtonMode(final byte[] payload) {
|
||||
switch (payload[1]) {
|
||||
case 0x0c:
|
||||
return handleSpeakToChatEnabled(payload);
|
||||
case 0x0d:
|
||||
return handleQuickAccess(payload);
|
||||
}
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public List<? extends GBDeviceEvent> handleVoiceNotifications(final byte[] payload) {
|
||||
if (payload.length != 4) {
|
||||
LOG.warn("Unexpected payload length {}", payload.length);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
boolean enabled;
|
||||
|
||||
// reversed?
|
||||
switch (payload[2]) {
|
||||
case 0x00:
|
||||
enabled = true;
|
||||
break;
|
||||
case 0x01:
|
||||
enabled = false;
|
||||
break;
|
||||
default:
|
||||
LOG.warn("Unknown voice notifications code {}", String.format("%02x", payload[3]));
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
LOG.debug("Voice Notifications: {}", enabled);
|
||||
|
||||
final GBDeviceEventUpdatePreferences event = new GBDeviceEventUpdatePreferences()
|
||||
.withPreferences(new VoiceNotifications(enabled).toPreferences());
|
||||
|
||||
return Collections.singletonList(event);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ButtonModes.Mode decodeButtonMode(final byte b) {
|
||||
switch (b) {
|
||||
case (byte) 0xff:
|
||||
return ButtonModes.Mode.OFF;
|
||||
case (byte) 0x35: // Seems to be the only one that differs?
|
||||
return ButtonModes.Mode.AMBIENT_SOUND_CONTROL;
|
||||
case (byte) 0x20:
|
||||
return ButtonModes.Mode.PLAYBACK_CONTROL;
|
||||
case (byte) 0x10:
|
||||
return ButtonModes.Mode.VOLUME_CONTROL;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte encodeButtonMode(final ButtonModes.Mode buttonMode) {
|
||||
switch (buttonMode) {
|
||||
case OFF:
|
||||
return (byte) 0xff;
|
||||
case AMBIENT_SOUND_CONTROL:
|
||||
return (byte) 0x35; // Seems to be the only one that differs?
|
||||
case PLAYBACK_CONTROL:
|
||||
return (byte) 0x20;
|
||||
case VOLUME_CONTROL:
|
||||
return (byte) 0x10;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Unknown button mode " + buttonMode);
|
||||
}
|
||||
}
|
@ -19,12 +19,15 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.pro
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.assertPrefs;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.assertRequest;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.assertRequests;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.handleMessage;
|
||||
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -35,10 +38,18 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePref
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.coordinators.SonyWF1000XM4Coordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AudioUpsampling;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AutomaticPowerOff;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerCustomBands;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerPreset;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.PauseWhenTakenOff;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatEnabled;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.BatteryType;
|
||||
|
||||
public class SonyProtocolImplV2Test {
|
||||
private final SonyProtocolImplV2 protocol = new SonyProtocolImplV2(null) {
|
||||
@ -56,12 +67,25 @@ public class SonyProtocolImplV2Test {
|
||||
@Test
|
||||
public void setAmbientSoundControl() {
|
||||
// TODO
|
||||
final Request request = protocol.setAmbientSoundControl(new AmbientSoundControl(
|
||||
AmbientSoundControl.Mode.WIND_NOISE_REDUCTION,
|
||||
true,
|
||||
15
|
||||
));
|
||||
assertRequest(request, "3e:0c:00:00:00:00:08:68:15:01:01:00:03:01:0f:a6:3c");
|
||||
//final Request request = protocol.setAmbientSoundControl(new AmbientSoundControl(
|
||||
// AmbientSoundControl.Mode.WIND_NOISE_REDUCTION,
|
||||
// true,
|
||||
// 15
|
||||
//));
|
||||
//assertRequest(request, "3e:0c:00:00:00:00:08:68:15:01:01:00:03:01:0f:a6:3c");
|
||||
|
||||
//final Map<AmbientSoundControl, String> commands = new LinkedHashMap<AmbientSoundControl, String>() {{
|
||||
// put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, false, 20), "68:17:01:01:01:00:14");
|
||||
// put(new AmbientSoundControl(AmbientSoundControl.Mode.OFF, false, 20), "68:17:01:00:00:00:14");
|
||||
// put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, false, 10), "68:17:01:01:01:00:0a");
|
||||
// put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, true, 20), "68:17:01:01:01:01:14");
|
||||
// put(new AmbientSoundControl(AmbientSoundControl.Mode.NOISE_CANCELLING, false, 20), "68:17:01:01:00:00:14");
|
||||
//}};
|
||||
//
|
||||
//for (Map.Entry<AmbientSoundControl, String> entry : commands.entrySet()) {
|
||||
// final Request request2 = protocol.setAmbientSoundControl(entry.getKey());
|
||||
// assertRequest(request2, 0x0c, entry.getValue());
|
||||
//}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -77,7 +101,16 @@ public class SonyProtocolImplV2Test {
|
||||
|
||||
@Test
|
||||
public void getBattery() {
|
||||
// TODO
|
||||
final Map<BatteryType, String> commands = new LinkedHashMap<BatteryType, String>() {{
|
||||
put(BatteryType.SINGLE, "22:00");
|
||||
put(BatteryType.DUAL, "22:09");
|
||||
put(BatteryType.CASE, "22:0a");
|
||||
}};
|
||||
|
||||
for (Map.Entry<BatteryType, String> entry : commands.entrySet()) {
|
||||
final Request request = protocol.getBattery(entry.getKey());
|
||||
assertRequest(request, 0x0c, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -85,6 +118,27 @@ public class SonyProtocolImplV2Test {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSpeakToChatEnabled() {
|
||||
assertRequests(protocol::setSpeakToChatEnabled, new LinkedHashMap<SpeakToChatEnabled, String>() {{
|
||||
put(new SpeakToChatEnabled(false), "f8:0c:01:01");
|
||||
put(new SpeakToChatEnabled(true), "f8:0c:00:01");
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSpeakToChatConfig() {
|
||||
assertRequests(protocol::setSpeakToChatConfig, new LinkedHashMap<SpeakToChatConfig, String>() {{
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.HIGH, SpeakToChatConfig.Timeout.STANDARD), "fc:0c:01:01");
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.LOW, SpeakToChatConfig.Timeout.STANDARD), "fc:0c:02:01");
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.STANDARD), "fc:0c:00:01");
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.SHORT), "fc:0c:00:00");
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.LONG), "fc:0c:00:02");
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.OFF), "fc:0c:00:03");
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.STANDARD), "fc:0c:00:01");
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAudioUpsampling() {
|
||||
// TODO
|
||||
@ -106,7 +160,10 @@ public class SonyProtocolImplV2Test {
|
||||
|
||||
@Test
|
||||
public void setAutomaticPowerOff() {
|
||||
// TODO
|
||||
assertRequests(protocol::setAutomaticPowerOff, new LinkedHashMap<AutomaticPowerOff, String>() {{
|
||||
put(AutomaticPowerOff.OFF, "28:05:11:00");
|
||||
put(AutomaticPowerOff.WHEN_TAKEN_OFF, "28:05:10:00");
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -119,6 +176,47 @@ public class SonyProtocolImplV2Test {
|
||||
// TODO
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getQuickAccess() {
|
||||
final Request request = protocol.getQuickAccess();
|
||||
assertRequest(request, "3e:0c:00:00:00:00:02:f6:0d:11:3c");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setQuickAccess() {
|
||||
final Map<QuickAccess, String> commands = new LinkedHashMap<QuickAccess, String>() {{
|
||||
put(new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.OFF), "3e:0c:01:00:00:00:05:f8:0d:02:00:00:19:3c");
|
||||
put(new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.SPOTIFY), "3e:0c:00:00:00:00:05:f8:0d:02:00:01:19:3c");
|
||||
put(new QuickAccess(QuickAccess.Mode.SPOTIFY, QuickAccess.Mode.OFF), "3e:0c:00:00:00:00:05:f8:0d:02:01:00:19:3c");
|
||||
}};
|
||||
|
||||
for (Map.Entry<QuickAccess, String> entry : commands.entrySet()) {
|
||||
final Request request = protocol.setQuickAccess(entry.getKey());
|
||||
assertRequest(request, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAmbientSoundControlButtonMode() {
|
||||
final Request request = protocol.getAmbientSoundControlButtonMode();
|
||||
assertRequest(request, "3e:0c:00:00:00:00:02:fa:03:0b:3c");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAmbientSoundControlButtonMode() {
|
||||
final Map<AmbientSoundControlButtonMode, String> commands = new LinkedHashMap<AmbientSoundControlButtonMode, String>() {{
|
||||
put(AmbientSoundControlButtonMode.NC_AS_OFF, "3e:0c:00:00:00:00:07:fc:03:01:35:01:00:01:4a:3c");
|
||||
put(AmbientSoundControlButtonMode.NC_AS, "3e:0c:01:00:00:00:07:fc:03:01:35:01:00:02:4c:3c");
|
||||
put(AmbientSoundControlButtonMode.NC_OFF, "3e:0c:01:00:00:00:07:fc:03:01:35:01:00:03:4d:3c");
|
||||
put(AmbientSoundControlButtonMode.AS_OFF, "3e:0c:01:00:00:00:07:fc:03:01:35:01:00:04:4e:3c");
|
||||
}};
|
||||
|
||||
for (Map.Entry<AmbientSoundControlButtonMode, String> entry : commands.entrySet()) {
|
||||
final Request request = protocol.setAmbientSoundControlButtonMode(entry.getKey());
|
||||
assertRequest(request, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPauseWhenTakenOff() {
|
||||
// TODO
|
||||
@ -126,11 +224,10 @@ public class SonyProtocolImplV2Test {
|
||||
|
||||
@Test
|
||||
public void setPauseWhenTakenOff() {
|
||||
final Request requestEnabled = protocol.setPauseWhenTakenOff(new PauseWhenTakenOff(true));
|
||||
assertRequest(requestEnabled, "3e:0c:01:00:00:00:03:f8:01:00:09:3c");
|
||||
|
||||
final Request requestDisabled = protocol.setPauseWhenTakenOff(new PauseWhenTakenOff(false));
|
||||
assertRequest(requestDisabled, "3e:0c:00:00:00:00:03:f8:01:01:09:3c");
|
||||
assertRequests(protocol::setPauseWhenTakenOff, new LinkedHashMap<PauseWhenTakenOff, String>() {{
|
||||
put(new PauseWhenTakenOff(false), "f8:01:01");
|
||||
put(new PauseWhenTakenOff(true), "f8:01:00");
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -164,7 +261,14 @@ public class SonyProtocolImplV2Test {
|
||||
|
||||
@Test
|
||||
public void setEqualizerCustomBands() {
|
||||
// TODO
|
||||
assertRequests(protocol::setEqualizerCustomBands, new LinkedHashMap<EqualizerCustomBands, String>() {{
|
||||
put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 3, 1), 0), "58:00:a0:06:0a:0a:0b:0c:0d:0b");
|
||||
put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 3, 5), 0), "58:00:a0:06:0a:0a:0b:0c:0d:0f");
|
||||
put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 4, 5), 0), "58:00:a0:06:0a:0a:0b:0c:0e:0f");
|
||||
put(new EqualizerCustomBands(Arrays.asList(5, 1, 2, 3, 5), 0), "58:00:a0:06:0a:0f:0b:0c:0d:0f");
|
||||
put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 3, 5), -6), "58:00:a0:06:04:0a:0b:0c:0d:0f");
|
||||
put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 3, 5), 10), "58:00:a0:06:14:0a:0b:0c:0d:0f");
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -203,8 +307,11 @@ public class SonyProtocolImplV2Test {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void setVoiceNotifications() {
|
||||
assertRequests(protocol::setVoiceNotifications, 0x0e, new LinkedHashMap<VoiceNotifications, String>() {{
|
||||
put(new VoiceNotifications(false), "48:01:01");
|
||||
put(new VoiceNotifications(true), "48:01:00");
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -213,8 +320,9 @@ public class SonyProtocolImplV2Test {
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore("Not implemented on V2")
|
||||
public void powerOff() {
|
||||
final Request request = protocol.powerOff();
|
||||
assertRequest(request, 0x0c, "24:03:01");
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -371,4 +479,47 @@ public class SonyProtocolImplV2Test {
|
||||
@Ignore("Not implemented on V2")
|
||||
public void handleVoiceNotifications() {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleQuickAccess() {
|
||||
final Map<String, QuickAccess> commands = new LinkedHashMap<String, QuickAccess>() {{
|
||||
// Ret
|
||||
put("3e:0c:00:00:00:00:05:f7:0d:02:00:00:17:3c", new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.OFF));
|
||||
put("3e:0c:01:00:00:00:05:f7:0d:02:00:01:19:3c", new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.SPOTIFY));
|
||||
put("3e:0c:01:00:00:00:05:f7:0d:02:01:00:19:3c", new QuickAccess(QuickAccess.Mode.SPOTIFY, QuickAccess.Mode.OFF));
|
||||
|
||||
// Notify
|
||||
put("3e:0c:00:00:00:00:05:f9:0d:02:00:00:19:3c", new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.OFF));
|
||||
put("3e:0c:01:00:00:00:05:f9:0d:02:00:01:1b:3c", new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.SPOTIFY));
|
||||
put("3e:0c:01:00:00:00:05:f9:0d:02:01:00:1b:3c", new QuickAccess(QuickAccess.Mode.SPOTIFY, QuickAccess.Mode.OFF));
|
||||
}};
|
||||
|
||||
for (Map.Entry<String, QuickAccess> entry : commands.entrySet()) {
|
||||
final List<? extends GBDeviceEvent> events = handleMessage(protocol, entry.getKey());
|
||||
assertPrefs(events, entry.getValue().toPreferences());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAmbientSoundControlButtonMode() {
|
||||
final Map<AmbientSoundControlButtonMode, String> commands = new LinkedHashMap<AmbientSoundControlButtonMode, String>() {{
|
||||
// Notify
|
||||
put(AmbientSoundControlButtonMode.NC_AS_OFF, "3e:0c:01:00:00:00:07:fd:03:01:35:01:00:01:4c:3c");
|
||||
put(AmbientSoundControlButtonMode.NC_AS, "3e:0c:00:00:00:00:07:fd:03:01:35:01:00:02:4c:3c");
|
||||
put(AmbientSoundControlButtonMode.NC_OFF, "3e:0c:00:00:00:00:07:fd:03:01:35:01:00:03:4d:3c");
|
||||
put(AmbientSoundControlButtonMode.AS_OFF, "3e:0c:01:00:00:00:07:fd:03:01:35:01:00:04:4f:3c");
|
||||
}};
|
||||
|
||||
for (Map.Entry<AmbientSoundControlButtonMode, String> entry : commands.entrySet()) {
|
||||
final List<? extends GBDeviceEvent> events = handleMessage(protocol, entry.getValue());
|
||||
assertEquals("Expect 1 events", 1, events.size());
|
||||
final GBDeviceEventUpdatePreferences event = (GBDeviceEventUpdatePreferences) events.get(0);
|
||||
final Map<String, Object> expectedPrefs = entry.getKey().toPreferences();
|
||||
assertEquals("Expect 1 prefs", 1, expectedPrefs.size());
|
||||
final Object modePrefValue = expectedPrefs
|
||||
.get(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE);
|
||||
assertNotNull(modePrefValue);
|
||||
assertEquals(modePrefValue, event.preferences.get(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,241 +0,0 @@
|
||||
/* Copyright (C) 2022 José Rebelo
|
||||
|
||||
This file is part of Gadgetbridge.
|
||||
|
||||
Gadgetbridge is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
Gadgetbridge is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v3;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.assertPrefs;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.assertRequest;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.assertRequests;
|
||||
import static nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.SonyTestUtils.handleMessage;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.SonyHeadphonesCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControl;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AmbientSoundControlButtonMode;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.AutomaticPowerOff;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.EqualizerCustomBands;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.PauseWhenTakenOff;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.QuickAccess;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakToChatEnabled;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.MockSonyCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.v1.params.BatteryType;
|
||||
|
||||
public class SonyProtocolImplV3Test {
|
||||
private final MockSonyCoordinator coordinator = new MockSonyCoordinator();
|
||||
private final SonyProtocolImplV3 protocol = new SonyProtocolImplV3(null) {
|
||||
@Override
|
||||
protected SonyHeadphonesCoordinator getCoordinator() {
|
||||
return coordinator;
|
||||
}
|
||||
};
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
coordinator.getCapabilities().clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAmbientSoundControl() {
|
||||
final Map<AmbientSoundControl, String> commands = new LinkedHashMap<AmbientSoundControl, String>() {{
|
||||
put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, false, 20), "68:17:01:01:01:00:14");
|
||||
put(new AmbientSoundControl(AmbientSoundControl.Mode.OFF, false, 20), "68:17:01:00:00:00:14");
|
||||
put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, false, 10), "68:17:01:01:01:00:0a");
|
||||
put(new AmbientSoundControl(AmbientSoundControl.Mode.AMBIENT_SOUND, true, 20), "68:17:01:01:01:01:14");
|
||||
put(new AmbientSoundControl(AmbientSoundControl.Mode.NOISE_CANCELLING, false, 20), "68:17:01:01:00:00:14");
|
||||
}};
|
||||
|
||||
for (Map.Entry<AmbientSoundControl, String> entry : commands.entrySet()) {
|
||||
final Request request = protocol.setAmbientSoundControl(entry.getKey());
|
||||
assertRequest(request, 0x0c, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSpeakToChatEnabled() {
|
||||
assertRequests(protocol::setSpeakToChatEnabled, new LinkedHashMap<SpeakToChatEnabled, String>() {{
|
||||
put(new SpeakToChatEnabled(false), "f8:0c:01:01");
|
||||
put(new SpeakToChatEnabled(true), "f8:0c:00:01");
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setSpeakToChatConfig() {
|
||||
assertRequests(protocol::setSpeakToChatConfig, new LinkedHashMap<SpeakToChatConfig, String>() {{
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.HIGH, SpeakToChatConfig.Timeout.STANDARD), "fc:0c:01:01");
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.LOW, SpeakToChatConfig.Timeout.STANDARD), "fc:0c:02:01");
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.STANDARD), "fc:0c:00:01");
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.SHORT), "fc:0c:00:00");
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.LONG), "fc:0c:00:02");
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.OFF), "fc:0c:00:03");
|
||||
put(new SpeakToChatConfig(false, SpeakToChatConfig.Sensitivity.AUTO, SpeakToChatConfig.Timeout.STANDARD), "fc:0c:00:01");
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBattery() {
|
||||
final Map<BatteryType, String> commands = new LinkedHashMap<BatteryType, String>() {{
|
||||
put(BatteryType.SINGLE, "22:00");
|
||||
put(BatteryType.DUAL, "22:09");
|
||||
put(BatteryType.CASE, "22:0a");
|
||||
}};
|
||||
|
||||
for (Map.Entry<BatteryType, String> entry : commands.entrySet()) {
|
||||
final Request request = protocol.getBattery(entry.getKey());
|
||||
assertRequest(request, 0x0c, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getQuickAccess() {
|
||||
final Request request = protocol.getQuickAccess();
|
||||
assertRequest(request, "3e:0c:00:00:00:00:02:f6:0d:11:3c");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setQuickAccess() {
|
||||
final Map<QuickAccess, String> commands = new LinkedHashMap<QuickAccess, String>() {{
|
||||
put(new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.OFF), "3e:0c:01:00:00:00:05:f8:0d:02:00:00:19:3c");
|
||||
put(new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.SPOTIFY), "3e:0c:00:00:00:00:05:f8:0d:02:00:01:19:3c");
|
||||
put(new QuickAccess(QuickAccess.Mode.SPOTIFY, QuickAccess.Mode.OFF), "3e:0c:00:00:00:00:05:f8:0d:02:01:00:19:3c");
|
||||
}};
|
||||
|
||||
for (Map.Entry<QuickAccess, String> entry : commands.entrySet()) {
|
||||
final Request request = protocol.setQuickAccess(entry.getKey());
|
||||
assertRequest(request, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getAmbientSoundControlButtonMode() {
|
||||
final Request request = protocol.getAmbientSoundControlButtonMode();
|
||||
assertRequest(request, "3e:0c:00:00:00:00:02:fa:03:0b:3c");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAmbientSoundControlButtonMode() {
|
||||
final Map<AmbientSoundControlButtonMode, String> commands = new LinkedHashMap<AmbientSoundControlButtonMode, String>() {{
|
||||
put(AmbientSoundControlButtonMode.NC_AS_OFF, "3e:0c:00:00:00:00:07:fc:03:01:35:01:00:01:4a:3c");
|
||||
put(AmbientSoundControlButtonMode.NC_AS, "3e:0c:01:00:00:00:07:fc:03:01:35:01:00:02:4c:3c");
|
||||
put(AmbientSoundControlButtonMode.NC_OFF, "3e:0c:01:00:00:00:07:fc:03:01:35:01:00:03:4d:3c");
|
||||
put(AmbientSoundControlButtonMode.AS_OFF, "3e:0c:01:00:00:00:07:fc:03:01:35:01:00:04:4e:3c");
|
||||
}};
|
||||
|
||||
for (Map.Entry<AmbientSoundControlButtonMode, String> entry : commands.entrySet()) {
|
||||
final Request request = protocol.setAmbientSoundControlButtonMode(entry.getKey());
|
||||
assertRequest(request, entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setPauseWhenTakenOff() {
|
||||
assertRequests(protocol::setPauseWhenTakenOff, new LinkedHashMap<PauseWhenTakenOff, String>() {{
|
||||
put(new PauseWhenTakenOff(false), "f8:01:01");
|
||||
put(new PauseWhenTakenOff(true), "f8:01:00");
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setEqualizerCustomBands() {
|
||||
assertRequests(protocol::setEqualizerCustomBands, new LinkedHashMap<EqualizerCustomBands, String>() {{
|
||||
put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 3, 1), 0), "58:00:a0:06:0a:0a:0b:0c:0d:0b");
|
||||
put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 3, 5), 0), "58:00:a0:06:0a:0a:0b:0c:0d:0f");
|
||||
put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 4, 5), 0), "58:00:a0:06:0a:0a:0b:0c:0e:0f");
|
||||
put(new EqualizerCustomBands(Arrays.asList(5, 1, 2, 3, 5), 0), "58:00:a0:06:0a:0f:0b:0c:0d:0f");
|
||||
put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 3, 5), -6), "58:00:a0:06:04:0a:0b:0c:0d:0f");
|
||||
put(new EqualizerCustomBands(Arrays.asList(0, 1, 2, 3, 5), 10), "58:00:a0:06:14:0a:0b:0c:0d:0f");
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAutomaticPowerOff() {
|
||||
assertRequests(protocol::setAutomaticPowerOff, new LinkedHashMap<AutomaticPowerOff, String>() {{
|
||||
put(AutomaticPowerOff.OFF, "28:05:11:00");
|
||||
put(AutomaticPowerOff.WHEN_TAKEN_OFF, "28:05:10:00");
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setVoiceNotifications() {
|
||||
assertRequests(protocol::setVoiceNotifications, 0x0e, new LinkedHashMap<VoiceNotifications, String>() {{
|
||||
put(new VoiceNotifications(false), "48:01:01");
|
||||
put(new VoiceNotifications(true), "48:01:00");
|
||||
}});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void powerOff() {
|
||||
final Request request = protocol.powerOff();
|
||||
assertRequest(request, 0x0c, "24:03:01");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleQuickAccess() {
|
||||
final Map<String, QuickAccess> commands = new LinkedHashMap<String, QuickAccess>() {{
|
||||
// Ret
|
||||
put("3e:0c:00:00:00:00:05:f7:0d:02:00:00:17:3c", new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.OFF));
|
||||
put("3e:0c:01:00:00:00:05:f7:0d:02:00:01:19:3c", new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.SPOTIFY));
|
||||
put("3e:0c:01:00:00:00:05:f7:0d:02:01:00:19:3c", new QuickAccess(QuickAccess.Mode.SPOTIFY, QuickAccess.Mode.OFF));
|
||||
|
||||
// Notify
|
||||
put("3e:0c:00:00:00:00:05:f9:0d:02:00:00:19:3c", new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.OFF));
|
||||
put("3e:0c:01:00:00:00:05:f9:0d:02:00:01:1b:3c", new QuickAccess(QuickAccess.Mode.OFF, QuickAccess.Mode.SPOTIFY));
|
||||
put("3e:0c:01:00:00:00:05:f9:0d:02:01:00:1b:3c", new QuickAccess(QuickAccess.Mode.SPOTIFY, QuickAccess.Mode.OFF));
|
||||
}};
|
||||
|
||||
for (Map.Entry<String, QuickAccess> entry : commands.entrySet()) {
|
||||
final List<? extends GBDeviceEvent> events = handleMessage(protocol, entry.getKey());
|
||||
assertPrefs(events, entry.getValue().toPreferences());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleAmbientSoundControlButtonMode() {
|
||||
final Map<AmbientSoundControlButtonMode, String> commands = new LinkedHashMap<AmbientSoundControlButtonMode, String>() {{
|
||||
// Notify
|
||||
put(AmbientSoundControlButtonMode.NC_AS_OFF, "3e:0c:01:00:00:00:07:fd:03:01:35:01:00:01:4c:3c");
|
||||
put(AmbientSoundControlButtonMode.NC_AS, "3e:0c:00:00:00:00:07:fd:03:01:35:01:00:02:4c:3c");
|
||||
put(AmbientSoundControlButtonMode.NC_OFF, "3e:0c:00:00:00:00:07:fd:03:01:35:01:00:03:4d:3c");
|
||||
put(AmbientSoundControlButtonMode.AS_OFF, "3e:0c:01:00:00:00:07:fd:03:01:35:01:00:04:4f:3c");
|
||||
}};
|
||||
|
||||
for (Map.Entry<AmbientSoundControlButtonMode, String> entry : commands.entrySet()) {
|
||||
final List<? extends GBDeviceEvent> events = handleMessage(protocol, entry.getValue());
|
||||
assertEquals("Expect 1 events", 1, events.size());
|
||||
final GBDeviceEventUpdatePreferences event = (GBDeviceEventUpdatePreferences) events.get(0);
|
||||
final Map<String, Object> expectedPrefs = entry.getKey().toPreferences();
|
||||
assertEquals("Expect 1 prefs", 1, expectedPrefs.size());
|
||||
final Object modePrefValue = expectedPrefs
|
||||
.get(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE);
|
||||
assertNotNull(modePrefValue);
|
||||
assertEquals(modePrefValue, event.preferences.get(DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user