1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-06-16 10:00:08 +02:00
Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/sony/headphones/SonyHeadphonesProtocol.java
2022-12-30 18:07:18 +00:00

318 lines
14 KiB
Java

/* Copyright (C) 2021 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;
import android.content.SharedPreferences;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceState;
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.ButtonModes;
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.VoiceNotifications;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SoundPosition;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.SurroundMode;
import nodomain.freeyourgadget.gadgetbridge.devices.sony.headphones.prefs.TouchSensor;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Request;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.Message;
import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.protocol.MessageType;
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 {
private static final Logger LOG = LoggerFactory.getLogger(SonyHeadphonesProtocol.class);
private byte sequenceNumber = 0;
// Request queue, sent every ACK, as we can't send them all at once
// Initially, it should contain all the init requests
private final Queue<Request> requestQueue = new LinkedList<>();
private int pendingAcks = 0;
private AbstractSonyProtocolImpl protocolImpl = null;
public SonyHeadphonesProtocol(GBDevice device) {
super(device);
}
@Override
public GBDeviceEvent[] decodeResponse(byte[] res) {
final Message message = Message.fromBytes(res);
if (message == null) {
return null;
}
LOG.info("Received {}", message);
final MessageType messageType = message.getType();
if (messageType == MessageType.ACK) {
if (sequenceNumber == message.getSequenceNumber()) {
LOG.warn("Unexpected ACK sequence number {}", message.getSequenceNumber());
return null;
}
sequenceNumber = message.getSequenceNumber();
return new GBDeviceEvent[]{handleAck()};
}
if (message.getPayload().length == 0) {
LOG.warn("Empty message: {}", message);
return null;
}
final List<Object> events = new ArrayList<>();
if (protocolImpl == null) {
// Check if we got an init response, which should indicate the protocol version
if (MessageType.COMMAND_1.equals(messageType) && message.getPayload()[0] == 0x01) {
// Init reply, set the protocol version
if (message.getPayload().length == 4) {
// WH-1000XM3: 01:00:40:10
// WF-SP800N 1.0.1: 01:00:70:00
protocolImpl = new SonyProtocolImplV1(getDevice());
} 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
protocolImpl = new SonyProtocolImplV2(getDevice());
break;
case 0x03:
// LinkBuds S 2.0.2: 01:00:03:00:00:07:00:00
default:
protocolImpl = new SonyProtocolImplV3(getDevice());
return null;
}
} else {
LOG.error("Unexpected init response payload length: {}", message.getPayload().length);
return null;
}
}
}
if (protocolImpl == null) {
LOG.error("No protocol implementation, ignoring message");
return null;
}
try {
switch (messageType) {
case COMMAND_1:
case COMMAND_2:
events.add(new GBDeviceEventSendBytes(encodeAck(message.getSequenceNumber())));
events.addAll(protocolImpl.handlePayload(messageType, message.getPayload()));
break;
default:
LOG.warn("Unknown message type for {}", message);
return null;
}
} catch (final Exception e) {
// Don't crash the app if we somehow fail to handle the payload
LOG.error("Error handling payload", e);
}
return events.toArray(new GBDeviceEvent[0]);
}
@Override
public byte[] encodeSendConfiguration(String config) {
if (protocolImpl == null) {
LOG.error("No protocol implementation, ignoring config {}", config);
return super.encodeSendConfiguration(config);
}
final SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress());
final Request configRequest;
switch (config) {
case DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL:
case DeviceSettingsPreferenceConst.PREF_SONY_FOCUS_VOICE:
case DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_LEVEL:
configRequest = protocolImpl.setAmbientSoundControl(AmbientSoundControl.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_AMBIENT_SOUND_CONTROL_BUTTON_MODE:
configRequest = protocolImpl.setAmbientSoundControlButtonMode(AmbientSoundControlButtonMode.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_NOISE_OPTIMIZER_START:
configRequest = protocolImpl.startNoiseCancellingOptimizer(true);
break;
case DeviceSettingsPreferenceConst.PREF_SONY_NOISE_OPTIMIZER_CANCEL:
configRequest = protocolImpl.startNoiseCancellingOptimizer(false);
break;
case DeviceSettingsPreferenceConst.PREF_SONY_SOUND_POSITION:
configRequest = protocolImpl.setSoundPosition(SoundPosition.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_SURROUND_MODE:
configRequest = protocolImpl.setSurroundMode(SurroundMode.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_MODE:
configRequest = protocolImpl.setEqualizerPreset(EqualizerPreset.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_400:
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_1000:
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_2500:
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_6300:
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BAND_16000:
case DeviceSettingsPreferenceConst.PREF_SONY_EQUALIZER_BASS:
configRequest = protocolImpl.setEqualizerCustomBands(EqualizerCustomBands.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_AUDIO_UPSAMPLING:
configRequest = protocolImpl.setAudioUpsampling(AudioUpsampling.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_TOUCH_SENSOR:
configRequest = protocolImpl.setTouchSensor(TouchSensor.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_AUTOMATIC_POWER_OFF:
configRequest = protocolImpl.setAutomaticPowerOff(AutomaticPowerOff.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_LEFT:
case DeviceSettingsPreferenceConst.PREF_SONY_BUTTON_MODE_RIGHT:
configRequest = protocolImpl.setButtonModes(ButtonModes.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_QUICK_ACCESS_DOUBLE_TAP:
case DeviceSettingsPreferenceConst.PREF_SONY_QUICK_ACCESS_TRIPLE_TAP:
configRequest = protocolImpl.setQuickAccess(QuickAccess.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_PAUSE_WHEN_TAKEN_OFF:
configRequest = protocolImpl.setPauseWhenTakenOff(PauseWhenTakenOff.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_NOTIFICATION_VOICE_GUIDE:
configRequest = protocolImpl.setVoiceNotifications(VoiceNotifications.fromPreferences(prefs));
break;
case DeviceSettingsPreferenceConst.PREF_SONY_CONNECT_TWO_DEVICES:
LOG.warn("Connection to two devices not implemented ('{}')", config);
return super.encodeSendConfiguration(config);
case DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT:
case DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_SENSITIVITY:
case DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_FOCUS_ON_VOICE:
case DeviceSettingsPreferenceConst.PREF_SONY_SPEAK_TO_CHAT_TIMEOUT:
LOG.warn("Speak-to-chat is not implemented ('{}')", config);
return super.encodeSendConfiguration(config);
default:
LOG.warn("Unknown config '{}'", config);
return super.encodeSendConfiguration(config);
}
if (configRequest == null) {
LOG.warn("Failed to encode config request for {}", config);
return super.encodeSendConfiguration(config);
}
pendingAcks++;
return configRequest.encode(sequenceNumber);
}
@Override
public byte[] encodeTestNewFunction() {
//return Request.fromHex(MessageType.COMMAND_1, "c40100").encode(sequenceNumber);
return null;
}
@Override
public byte[] encodePowerOff() {
if (protocolImpl != null) {
return protocolImpl.powerOff().encode(sequenceNumber);
}
return super.encodePowerOff();
}
public byte[] encodeAck(byte sequenceNumber) {
return new Message(MessageType.ACK, (byte) (1 - sequenceNumber), new byte[0]).encode();
}
public byte[] encodeInit() {
pendingAcks++;
return new Message(
MessageType.COMMAND_1,
sequenceNumber,
new byte[]{
(byte) 0x00,
(byte) 0x00
}
).encode();
}
public void enqueueRequests(final List<Request> requests) {
LOG.debug("Enqueueing {} requests", requests.size());
requestQueue.addAll(requests);
}
public int getPendingAcks() {
return pendingAcks;
}
public void decreasePendingAcks() {
pendingAcks--;
}
public byte[] getFromQueue() {
return requestQueue.remove().encode(sequenceNumber);
}
public boolean hasProtocolImplementation() {
return protocolImpl != null;
}
private GBDeviceEvent handleAck() {
pendingAcks--;
if (!requestQueue.isEmpty()) {
LOG.debug("Outstanding requests in queue: {}", requestQueue.size());
final Request request = requestQueue.remove();
return new GBDeviceEventSendBytes(request.encode(sequenceNumber));
}
if (GBDevice.State.INITIALIZING.equals(getDevice().getState())) {
// The queue is now empty, so we have got all the information from the device
// Mark it as initialized
return new GBDeviceEventUpdateDeviceState(GBDevice.State.INITIALIZED);
}
return null;
}
}