1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-30 14:02:56 +01:00

Sony WH-1000XM5: Add power off, fix battery, fix speak-to-chat fetch

This commit is contained in:
José Rebelo 2023-05-06 17:41:10 +01:00
parent 7b3fbeb4af
commit e0d481bb36
6 changed files with 76 additions and 16 deletions

View File

@ -48,7 +48,7 @@ public class SonyWH1000XM5Coordinator extends SonyHeadphonesCoordinator {
// TODO R.xml.devicesettings_connect_two_devices, // TODO R.xml.devicesettings_connect_two_devices,
// TODO automatic ANC depending on state (might need phone?) // TODO automatic ANC depending on state (might need phone?)
SonyHeadphonesCapabilities.BatterySingle, SonyHeadphonesCapabilities.BatterySingle,
// TODO SonyHeadphonesCapabilities.PowerOffFromPhone, SonyHeadphonesCapabilities.PowerOffFromPhone,
SonyHeadphonesCapabilities.AmbientSoundControl, SonyHeadphonesCapabilities.AmbientSoundControl,
SonyHeadphonesCapabilities.SpeakToChatEnabled, SonyHeadphonesCapabilities.SpeakToChatEnabled,
SonyHeadphonesCapabilities.SpeakToChatConfig, SonyHeadphonesCapabilities.SpeakToChatConfig,

View File

@ -25,6 +25,7 @@ public enum PayloadTypeV2 {
BATTERY_LEVEL_REQUEST(MessageType.COMMAND_1, 0x22), BATTERY_LEVEL_REQUEST(MessageType.COMMAND_1, 0x22),
BATTERY_LEVEL_REPLY(MessageType.COMMAND_1, 0x23), BATTERY_LEVEL_REPLY(MessageType.COMMAND_1, 0x23),
POWER_SET(MessageType.COMMAND_1, 0x24),
BATTERY_LEVEL_NOTIFY(MessageType.COMMAND_1, 0x25), BATTERY_LEVEL_NOTIFY(MessageType.COMMAND_1, 0x25),
AUTOMATIC_POWER_OFF_GET(MessageType.COMMAND_1, 0x26), AUTOMATIC_POWER_OFF_GET(MessageType.COMMAND_1, 0x26),

View File

@ -365,8 +365,14 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
@Override @Override
public Request powerOff() { public Request powerOff() {
LOG.warn("Power off not implemented for V2"); return new Request(
return null; PayloadTypeV2.POWER_SET.getMessageType(),
new byte[]{
PayloadTypeV2.POWER_SET.getCode(),
(byte) 0x03,
(byte) 0x01
}
);
} }
@Override @Override
@ -661,6 +667,8 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
@Override @Override
protected BatteryType decodeBatteryType(final byte b) { protected BatteryType decodeBatteryType(final byte b) {
switch (b) { switch (b) {
case 0x00:
return BatteryType.SINGLE;
case 0x09: case 0x09:
return BatteryType.DUAL; return BatteryType.DUAL;
case 0x0a: case 0x0a:
@ -673,10 +681,11 @@ public class SonyProtocolImplV2 extends SonyProtocolImplV1 {
@Override @Override
protected byte encodeBatteryType(final BatteryType batteryType) { protected byte encodeBatteryType(final BatteryType batteryType) {
switch (batteryType) { switch (batteryType) {
case SINGLE:
return 0x00;
case DUAL: case DUAL:
return 0x09; return 0x09;
case CASE: case CASE:
case SINGLE: // TODO: This is not the code for single, but we need to encode something
return 0x0a; return 0x0a;
} }

View File

@ -19,11 +19,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.pro
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType; import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
public enum PayloadTypeV3 { public enum PayloadTypeV3 {
QUICK_ACCESS_GET(MessageType.COMMAND_1, 0xf6),
QUICK_ACCESS_RET(MessageType.COMMAND_1, 0xf7),
QUICK_ACCESS_SET(MessageType.COMMAND_1, 0xf8),
QUICK_ACCESS_NOTIFY(MessageType.COMMAND_1, 0xf9),
AMBIENT_SOUND_CONTROL_BUTTON_MODE_GET(MessageType.COMMAND_1, 0xfa), 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_RET(MessageType.COMMAND_1, 0xfb),
AMBIENT_SOUND_CONTROL_BUTTON_MODE_SET(MessageType.COMMAND_1, 0xfc), AMBIENT_SOUND_CONTROL_BUTTON_MODE_SET(MessageType.COMMAND_1, 0xfc),

View File

@ -135,9 +135,9 @@ public class SonyProtocolImplV3 extends SonyProtocolImplV2 {
@Override @Override
public Request getQuickAccess() { public Request getQuickAccess() {
return new Request( return new Request(
PayloadTypeV3.QUICK_ACCESS_GET.getMessageType(), PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getMessageType(),
new byte[]{ new byte[]{
PayloadTypeV3.QUICK_ACCESS_GET.getCode(), PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_GET.getCode(),
(byte) 0x0d (byte) 0x0d
} }
); );
@ -146,9 +146,9 @@ public class SonyProtocolImplV3 extends SonyProtocolImplV2 {
@Override @Override
public Request setQuickAccess(final QuickAccess quickAccess) { public Request setQuickAccess(final QuickAccess quickAccess) {
return new Request( return new Request(
PayloadTypeV3.QUICK_ACCESS_SET.getMessageType(), PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getMessageType(),
new byte[]{ new byte[]{
PayloadTypeV3.QUICK_ACCESS_SET.getCode(), PayloadTypeV1.AUTOMATIC_POWER_OFF_BUTTON_MODE_SET.getCode(),
(byte) 0x0d, (byte) 0x0d,
(byte) 0x02, (byte) 0x02,
quickAccess.getModeDoubleTap().getCode(), quickAccess.getModeDoubleTap().getCode(),
@ -232,9 +232,6 @@ public class SonyProtocolImplV3 extends SonyProtocolImplV2 {
final PayloadTypeV3 payloadType = PayloadTypeV3.fromCode(messageType, payload[0]); final PayloadTypeV3 payloadType = PayloadTypeV3.fromCode(messageType, payload[0]);
switch (payloadType) { switch (payloadType) {
case QUICK_ACCESS_RET:
case QUICK_ACCESS_NOTIFY:
return handleQuickAccess(payload);
case AMBIENT_SOUND_CONTROL_BUTTON_MODE_RET: case AMBIENT_SOUND_CONTROL_BUTTON_MODE_RET:
case AMBIENT_SOUND_CONTROL_BUTTON_MODE_NOTIFY: case AMBIENT_SOUND_CONTROL_BUTTON_MODE_NOTIFY:
return handleAmbientSoundControlButtonMode(payload); return handleAmbientSoundControlButtonMode(payload);
@ -350,6 +347,43 @@ public class SonyProtocolImplV3 extends SonyProtocolImplV2 {
return Collections.singletonList(event); 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[3]);
if (disabled == null) {
LOG.warn("Unknown speak to chat enabled code {}", String.format("%02x", payload[3]));
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) { public List<? extends GBDeviceEvent> handleVoiceNotifications(final byte[] payload) {
if (payload.length != 4) { if (payload.length != 4) {
LOG.warn("Unexpected payload length {}", payload.length); LOG.warn("Unexpected payload length {}", payload.length);

View File

@ -46,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SpeakT
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.VoiceNotifications; 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.Request;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.impl.MockSonyCoordinator; 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 { public class SonyProtocolImplV3Test {
private final MockSonyCoordinator coordinator = new MockSonyCoordinator(); private final MockSonyCoordinator coordinator = new MockSonyCoordinator();
@ -98,6 +99,20 @@ public class SonyProtocolImplV3Test {
}}); }});
} }
@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 @Test
public void getQuickAccess() { public void getQuickAccess() {
final Request request = protocol.getQuickAccess(); final Request request = protocol.getQuickAccess();
@ -175,6 +190,12 @@ public class SonyProtocolImplV3Test {
}}); }});
} }
@Test
public void powerOff() {
final Request request = protocol.powerOff();
assertRequest(request, 0x0c, "24:03:01");
}
@Test @Test
public void handleQuickAccess() { public void handleQuickAccess() {
final Map<String, QuickAccess> commands = new LinkedHashMap<String, QuickAccess>() {{ final Map<String, QuickAccess> commands = new LinkedHashMap<String, QuickAccess>() {{