diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a68761b1e..45e81503c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -124,6 +124,10 @@ android:name=".activities.CalBlacklistActivity" android:label="@string/title_activity_calblacklist" android:parentActivityName=".activities.SettingsActivity" /> + . */ +package nodomain.freeyourgadget.gadgetbridge.devices.qc35; + +import android.app.Activity; +import android.content.Context; +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.Device; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; + +public class QC35Coordinator extends AbstractDeviceCoordinator { + @Override + protected void deleteDevice(@NonNull GBDevice gbDevice, @NonNull Device device, @NonNull DaoSession session) throws GBException { + + } + + @NonNull + @Override + public DeviceType getSupportedType(GBDeviceCandidate candidate) { + if (candidate.getName().startsWith("Bose QC 35")) { + return DeviceType.BOSE_QC35; + } + return DeviceType.UNKNOWN; + } + + @Override + public DeviceType getDeviceType() { + return DeviceType.BOSE_QC35; + } + + @Nullable + @Override + public Class getPairingActivity() { + return null; + } + + @Override + public int[] getSupportedDeviceSpecificSettings(GBDevice device) { + return new int[]{ + R.xml.devicesettings_qc35 + }; + } + + @Override + public boolean supportsActivityDataFetching() { + return false; + } + + @Override + public boolean supportsActivityTracking() { + return false; + } + + @Override + public SampleProvider getSampleProvider(GBDevice device, DaoSession session) { + return null; + } + + @Override + public InstallHandler findInstallHandler(Uri uri, Context context) { + return null; + } + + @Override + public boolean supportsScreenshots() { + return false; + } + + @Override + public int getAlarmSlotCount() { + return 0; + } + + @Override + public boolean supportsSmartWakeup(GBDevice device) { + return false; + } + + @Override + public boolean supportsHeartRateMeasurement(GBDevice device) { + return false; + } + + @Override + public String getManufacturer() { + return "Bose"; + } + + @Override + public boolean supportsAppsManagement() { + return false; + } + + @Override + public Class getAppsManagementActivity() { + return null; + } + + @Override + public boolean supportsCalendarEvents() { + return false; + } + + @Override + public boolean supportsRealtimeData() { + return false; + } + + @Override + public boolean supportsWeather() { + return false; + } + + @Override + public boolean supportsFindDevice() { + return false; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescControlActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescControlActivity.java new file mode 100644 index 000000000..d4d9de44f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescControlActivity.java @@ -0,0 +1,247 @@ +/* Copyright (C) 2021 Daniel Dakhno + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.devices.vesc; + +import android.content.Intent; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.R; +import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity; +import nodomain.freeyourgadget.gadgetbridge.service.devices.vesc.VescDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.util.Prefs; + +public class VescControlActivity extends AbstractGBActivity { + private static final String TAG = "VescControlActivity"; + private boolean volumeKeyPressed = false; + private boolean volumeKeysControl = false; + private int currentRPM = 0; + private int currentBreakCurrentMa = 0; + LocalBroadcastManager localBroadcastManager; + + private Logger logger = LoggerFactory.getLogger(getClass()); + + EditText rpmEditText, breakCurrentEditText; + + private final int DELAY_SAVE = 1000; + + Prefs preferences; + + final String PREFS_KEY_LAST_RPM = "VESC_LAST_RPM"; + final String PREFS_KEY_LAST_BREAK_CURRENT = "VESC_LAST_BREAK_CURRENT"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_vesc_control); + + localBroadcastManager = LocalBroadcastManager.getInstance(this); + + preferences = GBApplication.getPrefs(); + + initViews(); + + restoreValues(); + } + + private void restoreValues(){ + rpmEditText.setText(preferences.getInt(PREFS_KEY_LAST_RPM, 0)); + breakCurrentEditText.setText(preferences.getInt(PREFS_KEY_LAST_BREAK_CURRENT, 0)); + } + + @Override + protected void onPause() { + super.onPause(); + setCurrent(0); + } + + private boolean handleKeyPress(int keyCode, boolean isPressed) { + if (!volumeKeysControl) { + return false; + } + + if (keyCode != 24 && keyCode != 25) { + return false; + } + + if (volumeKeyPressed == isPressed) { + return true; + } + volumeKeyPressed = isPressed; + + logger.debug("volume " + (keyCode == 25 ? "down" : "up") + (isPressed ? " pressed" : " released")); + if (!isPressed) { + setCurrent(0); + return true; + } + if (keyCode == 24) { + setRPM(currentRPM); + } else { + setBreakCurrent(VescControlActivity.this.currentBreakCurrentMa); + } + + return true; + } + + Runnable rpmSaveRunnable = new Runnable() { + @Override + public void run() { + preferences.getPreferences().edit().putInt(PREFS_KEY_LAST_RPM, currentRPM).apply(); + } + }; + + Runnable breakCurrentSaveRunnable = new Runnable() { + @Override + public void run() { + preferences.getPreferences().edit().putInt(PREFS_KEY_LAST_BREAK_CURRENT, currentBreakCurrentMa).apply(); + } + }; + + private void initViews() { + ((CheckBox) findViewById(R.id.vesc_control_checkbox_volume_keys)) + .setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + VescControlActivity.this.volumeKeysControl = isChecked; + if (!isChecked) { + setRPM(0); + } + } + }); + + rpmEditText = ((EditText) findViewById(R.id.vesc_control_input_rpm)); + rpmEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + rpmEditText.removeCallbacks(rpmSaveRunnable); + rpmEditText.postDelayed(rpmSaveRunnable, DELAY_SAVE); + + String text = s.toString(); + if (text.isEmpty()) { + currentRPM = 0; + return; + } + VescControlActivity.this.currentRPM = Integer.parseInt(text); + } + }); + + breakCurrentEditText = ((EditText) findViewById(R.id.vesc_control_input_break_current)); + breakCurrentEditText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + breakCurrentEditText.removeCallbacks(breakCurrentSaveRunnable); + breakCurrentEditText.postDelayed(breakCurrentSaveRunnable, DELAY_SAVE); + + String text = s.toString(); + if (text.isEmpty()) { + currentBreakCurrentMa = 0; + return; + } + VescControlActivity.this.currentBreakCurrentMa = Integer.parseInt(text) * 1000; + } + }); + + View.OnTouchListener controlTouchListener = new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (v.getId() == R.id.vesc_control_button_fwd) { + setRPM(VescControlActivity.this.currentRPM); + } else { + setBreakCurrent(VescControlActivity.this.currentBreakCurrentMa); + } + } else if (event.getAction() == MotionEvent.ACTION_UP) { + setCurrent(0); + } else { + return false; + } + return true; + } + }; + + findViewById(R.id.vesc_control_button_fwd).setOnTouchListener(controlTouchListener); + findViewById(R.id.vesc_control_button_break).setOnTouchListener(controlTouchListener); + } + + private void setBreakCurrent(int breakCurrentMa) { + logger.debug("setting break current to {}", breakCurrentMa); + Intent intent = new Intent(VescDeviceSupport.COMMAND_SET_BREAK_CURRENT); + intent.putExtra(VescDeviceSupport.EXTRA_CURRENT, breakCurrentMa); + sendLocalBroadcast(intent); + } + + private void setCurrent(int currentMa) { + logger.debug("setting current to {}", currentMa); + Intent intent = new Intent(VescDeviceSupport.COMMAND_SET_CURRENT); + intent.putExtra(VescDeviceSupport.EXTRA_CURRENT, currentMa); + sendLocalBroadcast(intent); + } + + private void setRPM(int rpm) { + logger.debug("setting rpm to {}", rpm); + Intent intent = new Intent(VescDeviceSupport.COMMAND_SET_RPM); + intent.putExtra(VescDeviceSupport.EXTRA_RPM, rpm); + sendLocalBroadcast(intent); + } + + private void sendLocalBroadcast(Intent intent) { + localBroadcastManager.sendBroadcast(intent); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return handleKeyPress(keyCode, false); + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return handleKeyPress(keyCode, true); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java new file mode 100644 index 000000000..f34dc2083 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/vesc/VescCoordinator.java @@ -0,0 +1,157 @@ +/* Copyright (C) 2021 Daniel Dakhno + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.devices.vesc; + +import android.app.Activity; +import android.content.Context; +import android.net.Uri; +import android.os.ParcelUuid; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.GBException; +import nodomain.freeyourgadget.gadgetbridge.devices.AbstractDeviceCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler; +import nodomain.freeyourgadget.gadgetbridge.devices.SampleProvider; +import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession; +import nodomain.freeyourgadget.gadgetbridge.entities.Device; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate; +import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; + +public class VescCoordinator extends AbstractDeviceCoordinator { + public final static String UUID_SERVICE_SERIAL_HM10 = "0000ffe0-0000-1000-8000-00805f9b34fb"; + public final static String UUID_CHARACTERISTIC_SERIAL_TX_HM10 = "0000ffe1-0000-1000-8000-00805f9b34fb"; + + public final static String UUID_SERVICE_SERIAL_NRF = "0000ffe0-0000-1000-8000-00805f9b34fb"; + public final static String UUID_CHARACTERISTIC_SERIAL_TX_NRF = "0000ffe0-0000-1000-8000-00805f9b34fb"; + + + @Override + protected void deleteDevice(GBDevice gbDevice, Device device, DaoSession session) throws GBException { + + } + + @Override + public DeviceType getSupportedType(GBDeviceCandidate candidate) { + ParcelUuid[] uuids = candidate.getServiceUuids(); + Logger logger = LoggerFactory.getLogger(getClass()); + for(ParcelUuid uuid: uuids){ + logger.debug("service: {}", uuid.toString()); + } + for(ParcelUuid uuid : uuids){ + if(uuid.getUuid().toString().equals(UUID_SERVICE_SERIAL_NRF)){ + return DeviceType.VESC_NRF; + }else if(uuid.getUuid().toString().equals(UUID_SERVICE_SERIAL_HM10)){ + return DeviceType.VESC_HM10; + } + } + return DeviceType.UNKNOWN; + } + + @Override + public DeviceType getDeviceType() { + return DeviceType.VESC_HM10; // TODO: this limits this coordinator to NRF serial service + } + + @Override + public Class getPairingActivity() { + return null; + } + + @Override + public boolean supportsActivityDataFetching() { + return false; + } + + @Override + public int getBondingStyle() { + return BONDING_STYLE_NONE; + } + + @Override + public boolean supportsActivityTracking() { + return false; + } + + @Override + public SampleProvider getSampleProvider(GBDevice device, DaoSession session) { + return null; + } + + @Override + public InstallHandler findInstallHandler(Uri uri, Context context) { + return null; + } + + @Override + public boolean supportsScreenshots() { + return false; + } + + @Override + public int getAlarmSlotCount() { + return 0; + } + + @Override + public boolean supportsSmartWakeup(GBDevice device) { + return false; + } + + @Override + public boolean supportsHeartRateMeasurement(GBDevice device) { + return false; + } + + @Override + public String getManufacturer() { + return "Benjamin Vedder"; + } + + @Override + public boolean supportsAppsManagement() { + return true; + } + + @Override + public Class getAppsManagementActivity() { + return VescControlActivity.class; + } + + @Override + public boolean supportsCalendarEvents() { + return false; + } + + @Override + public boolean supportsRealtimeData() { + return false; + } + + @Override + public boolean supportsWeather() { + return false; + } + + @Override + public boolean supportsFindDevice() { + return false; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java index b9cd6e552..cd10334e9 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ActivitySummaryJsonSummary.java @@ -145,7 +145,7 @@ public class ActivitySummaryJsonSummary { private JSONObject createActivitySummaryGroups(){ String groupDefinitions = "{'Strokes':['averageStrokeDistance','averageStrokesPerSecond','strokes'], " + "'Swimming':['swolfIndex','swimStyle'], " + - "'Elevation':['ascentMeters','descentMeters','maxAltitude','minAltitude','averageAltitude','ascentSeconds','descentSeconds','flatSeconds', 'baseAltitude'], " + + "'Elevation':['ascentMeters','descentMeters','maxAltitude','minAltitude','averageAltitude', 'baseAltitude','ascentSeconds','descentSeconds','flatSeconds','ascentDistance','descentDistance','flatDistance'], " + "'Speed':['averageSpeed','maxSpeed','minSpeed','averageKMPaceSeconds','minPace','maxPace','averageSpeed2','averageCadence','maxCadence','minCadence'], " + "'Activity':['distanceMeters','steps','activeSeconds','caloriesBurnt','totalStride'," + "'averageHR','maxHR','minHR','averageStride','maxStride','minStride'], " + diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java index ff3dcc3c8..1b7f84755 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceType.java @@ -105,6 +105,9 @@ public enum DeviceType { GALAXY_BUDS(420, R.drawable.ic_device_galaxy_buds, R.drawable.ic_device_galaxy_buds_disabled, R.string.devicetype_galaxybuds), SONY_WH_1000XM3(430, R.drawable.ic_device_headphones, R.drawable.ic_device_headphones_disabled, R.string.devicetype_sony_wh_1000xm3), SONY_WF_SP800N(431, R.drawable.ic_device_galaxy_buds, R.drawable.ic_device_galaxy_buds_disabled, R.string.devicetype_sony_wf_sp800n), + BOSE_QC35(440, R.drawable.ic_device_headphones, R.drawable.ic_device_headphones_disabled, R.string.devicetype_bose_qc35), + VESC_NRF(500, R.drawable.ic_devices_other, R.drawable.ic_devices_other, R.string.devicetype_vesc), + VESC_HM10(501, R.drawable.ic_devices_other, R.drawable.ic_devices_other, R.string.devicetype_vesc), TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test); private final int key; diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java index bef134867..b35e55c97 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceSupportFactory.java @@ -85,6 +85,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.nothing.Ear1Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.nut.NutSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.PebbleSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.pinetime.PineTimeJFSupport; +import nodomain.freeyourgadget.gadgetbridge.service.devices.qc35.QC35BaseSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.roidmi.RoidmiSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.smaq2oss.SMAQ2OSSSupport; @@ -92,6 +93,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.sony.headphones.Sony import nodomain.freeyourgadget.gadgetbridge.service.devices.sonyswr12.SonySWR12DeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.tlw64.TLW64Support; import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support.UM25Support; +import nodomain.freeyourgadget.gadgetbridge.service.devices.vesc.VescDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.vibratissimo.VibratissimoSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.waspos.WaspOSDeviceSupport; import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport; @@ -376,6 +378,13 @@ public class DeviceSupportFactory { case SONY_WF_SP800N: deviceSupport = new ServiceDeviceSupport(new SonyHeadphonesSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); break; + case VESC_NRF: + case VESC_HM10: + deviceSupport = new ServiceDeviceSupport(new VescDeviceSupport(gbDevice.getType()), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); + break; + case BOSE_QC35: + deviceSupport = new ServiceDeviceSupport(new QC35BaseSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING)); + break; } if (deviceSupport != null) { deviceSupport.setContext(gbDevice, mBtAdapter, mContext); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java index c2f29a689..6205c8dc7 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pinetime/PineTimeJFSupport.java @@ -470,7 +470,7 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuL if (getSupportedServices().contains(PineTimeJFConstants.UUID_SERVICE_MOTION)) { builder.notify(getCharacteristic(PineTimeJFConstants.UUID_CHARACTERISTIC_MOTION_STEP_COUNT), true); - builder.notify(getCharacteristic(PineTimeJFConstants.UUID_CHARACTERISTIC_MOTION_RAW_XYZ_VALUES), true); + //builder.notify(getCharacteristic(PineTimeJFConstants.UUID_CHARACTERISTIC_MOTION_RAW_XYZ_VALUES), false); // issue #2527 } setInitialized(builder); @@ -635,7 +635,6 @@ public class PineTimeJFSupport extends AbstractBTLEDeviceSupport implements DfuL } else if (characteristicUUID.equals(PineTimeJFConstants.UUID_CHARACTERISTIC_MOTION_STEP_COUNT)) { int steps = BLETypeConversions.toUint32(characteristic.getValue()); if (LOG.isDebugEnabled()) { - GB.toast("Steps count: " + steps, Toast.LENGTH_SHORT, GB.INFO); LOG.debug("onCharacteristicChanged: MotionService:Steps=" + steps); } onReceiveStepsSample(steps); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35BaseSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35BaseSupport.java new file mode 100644 index 000000000..5efee2a84 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35BaseSupport.java @@ -0,0 +1,89 @@ +/* Copyright (C) 2021 Daniel Dakhno + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qc35; + +import android.net.Uri; + +import java.util.ArrayList; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport; +import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread; +import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; + +public class QC35BaseSupport extends AbstractSerialDeviceSupport { + + @Override + public void onSetAlarms(ArrayList alarms) { + + } + + @Override + public void onInstallApp(Uri uri) { + + } + + @Override + public void onAppConfiguration(UUID appUuid, String config, Integer id) { + + } + + @Override + public void onHeartRateTest() { + + } + + @Override + public void onSetConstantVibration(int integer) { + + } + + @Override + public void onSetHeartRateMeasurementInterval(int seconds) { + + } + + @Override + public void onReadConfiguration(String config) { + + } + + + + @Override + public boolean connect() { + getDeviceProtocol(); + getDeviceIOThread().start(); + return true; + } + + @Override + public boolean useAutoConnect() { + return false; + } + + @Override + protected GBDeviceProtocol createDeviceProtocol() { + return new QC35Protocol(getDevice()); + } + + @Override + protected GBDeviceIoThread createDeviceIOThread() { + return new QC35IOThread(getDevice(), getContext(), (QC35Protocol) createDeviceProtocol(), this, getBluetoothAdapter()); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35IOThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35IOThread.java new file mode 100644 index 000000000..964791bd1 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35IOThread.java @@ -0,0 +1,78 @@ +/* Copyright (C) 2021 Daniel Dakhno + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qc35; + +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.os.ParcelUuid; + +import androidx.annotation.NonNull; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.btclassic.BtClassicIoThread; +import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport; + +public class QC35IOThread extends BtClassicIoThread { + QC35Protocol protocol; + byte[] buffer = new byte[1024]; + + private Logger logger = LoggerFactory.getLogger(getClass()); + + public QC35IOThread(GBDevice gbDevice, Context context, QC35Protocol deviceProtocol, AbstractSerialDeviceSupport deviceSupport, BluetoothAdapter btAdapter) { + super(gbDevice, context, deviceProtocol, deviceSupport, btAdapter); + this.protocol = deviceProtocol; + } + + @NonNull + @Override + protected UUID getUuidToConnect(@NonNull ParcelUuid[] uuids) { + return UUID.fromString("00001101-0000-1000-8000-00805f9b34fb"); + } + + @Override + protected void initialize() { + super.initialize(); + + byte[] connectPayload = new byte[]{0x00, 0x01, 0x01, 0x00}; + byte[] ncPayload = protocol.encodeSendConfiguration(DeviceSettingsPreferenceConst.PREF_QC35_NOISE_CANCELLING_LEVEL); + byte[] batteryPayload = new byte[]{0x02, 0x02, 0x01, 0x00}; + byte[] packet = new byte[connectPayload.length + ncPayload.length + batteryPayload.length]; + System.arraycopy(connectPayload, 0, packet, 0, connectPayload.length); + System.arraycopy(ncPayload, 0, packet, connectPayload.length, ncPayload.length); + System.arraycopy(batteryPayload, 0, packet, ncPayload.length + connectPayload.length, batteryPayload.length); + + getDevice().setFirmwareVersion("0"); + + write(packet); + } + @Override + protected byte[] parseIncoming(InputStream inStream) throws IOException { + int size = inStream.read(buffer); + logger.debug("read bytes: {}", size); + byte[] actual = new byte[size]; + System.arraycopy(buffer, 0, actual, 0, size); + return actual; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35Protocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35Protocol.java new file mode 100644 index 000000000..c7f026a46 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qc35/QC35Protocol.java @@ -0,0 +1,90 @@ +/* Copyright (C) 2021 Daniel Dakhno + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.qc35; + +import android.content.SharedPreferences; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.ArrayList; + +import nodomain.freeyourgadget.gadgetbridge.GBApplication; +import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent; +import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol; +import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; + +public class QC35Protocol extends GBDeviceProtocol { + Logger logger = LoggerFactory.getLogger(getClass()); + protected QC35Protocol(GBDevice device) { + super(device); + } + + @Override + public GBDeviceEvent[] decodeResponse(byte[] responseData) { + logger.debug("response: {}", StringUtils.bytesToHex(responseData)); + + ArrayList events = new ArrayList<>(); + + ByteBuffer buffer = ByteBuffer.wrap(responseData); + while(buffer.remaining() > 0){ + int first = buffer.get(); + int second = buffer.get(); + int third = buffer.get(); + int length = buffer.get(); + byte[] data = new byte[length]; + buffer.get(data); + if(first == 0x02){ + if(second == 0x02){ + if(third == 0x03){ + GBDeviceEventBatteryInfo batteryInfo = new GBDeviceEventBatteryInfo(); + batteryInfo.level = data[0]; + events.add(batteryInfo); + } + } + } + } + + return events.toArray(new GBDeviceEvent[0]); + } + + @Override + public byte[] encodeTestNewFunction() { + return new byte[]{0x02, 0x02, 0x01, 0x00}; + } + + @Override + public byte[] encodeSendConfiguration(String config) { + SharedPreferences prefs = GBApplication.getDeviceSpecificSharedPrefs(getDevice().getAddress()); + + if(config.equals(DeviceSettingsPreferenceConst.PREF_QC35_NOISE_CANCELLING_LEVEL)){ + int level = prefs.getInt(config, 0); + if(level == 2){ + level = 1; + }else if(level == 1){ + level = 3; + } + return new byte[]{0x01, 0x06, 0x02, 0x01, (byte) level}; + } + + return null; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/CommandType.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/CommandType.java new file mode 100644 index 000000000..b1ce868b4 --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/CommandType.java @@ -0,0 +1,33 @@ +/* Copyright (C) 2021 Daniel Dakhno + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.vesc; + +public enum CommandType { + SET_CURRENT((byte) 0x06), + SET_CURRENT_BRAKE((byte) 0x07), + SET_RPM((byte) 0x08), + ; + byte commandByte; + + CommandType(byte commandByte){ + this.commandByte = commandByte; + } + + public byte getCommandByte(){ + return this.commandByte; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescBaseDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescBaseDeviceSupport.java new file mode 100644 index 000000000..07d918e9f --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescBaseDeviceSupport.java @@ -0,0 +1,198 @@ +/* Copyright (C) 2021 Daniel Dakhno + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.vesc; + +import android.net.Uri; + +import java.util.ArrayList; +import java.util.UUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import nodomain.freeyourgadget.gadgetbridge.model.Alarm; +import nodomain.freeyourgadget.gadgetbridge.model.CalendarEventSpec; +import nodomain.freeyourgadget.gadgetbridge.model.CallSpec; +import nodomain.freeyourgadget.gadgetbridge.model.CannedMessagesSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec; +import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; +import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; +import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport; + +public class VescBaseDeviceSupport extends AbstractBTLEDeviceSupport { + private static final Logger LOG = LoggerFactory.getLogger(VescBaseDeviceSupport.class); + + public VescBaseDeviceSupport() { + super(LOG); + } + + + @Override + public void onNotification(NotificationSpec notificationSpec) { + + } + + @Override + public void onDeleteNotification(int id) { + + } + + @Override + public void onSetTime() { + + } + + @Override + public void onSetAlarms(ArrayList alarms) { + + } + + @Override + public void onSetCallState(CallSpec callSpec) { + + } + + @Override + public void onSetCannedMessages(CannedMessagesSpec cannedMessagesSpec) { + + } + + @Override + public void onSetMusicState(MusicStateSpec stateSpec) { + + } + + @Override + public void onSetMusicInfo(MusicSpec musicSpec) { + + } + + @Override + public void onEnableRealtimeSteps(boolean enable) { + + } + + @Override + public void onInstallApp(Uri uri) { + + } + + @Override + public void onAppInfoReq() { + + } + + @Override + public void onAppStart(UUID uuid, boolean start) { + + } + + @Override + public void onAppDelete(UUID uuid) { + + } + + @Override + public void onAppConfiguration(UUID appUuid, String config, Integer id) { + + } + + @Override + public void onAppReorder(UUID[] uuids) { + + } + + @Override + public void onFetchRecordedData(int dataTypes) { + + } + + @Override + public void onReset(int flags) { + + } + + @Override + public void onHeartRateTest() { + + } + + @Override + public void onEnableRealtimeHeartRateMeasurement(boolean enable) { + + } + + @Override + public void onFindDevice(boolean start) { + + } + + @Override + public void onSetConstantVibration(int integer) { + + } + + @Override + public void onScreenshotReq() { + + } + + @Override + public void onEnableHeartRateSleepSupport(boolean enable) { + + } + + @Override + public void onSetHeartRateMeasurementInterval(int seconds) { + + } + + @Override + public void onAddCalendarEvent(CalendarEventSpec calendarEventSpec) { + + } + + @Override + public void onDeleteCalendarEvent(byte type, long id) { + + } + + @Override + public void onSendConfiguration(String config) { + + } + + @Override + public void onReadConfiguration(String config) { + + } + + @Override + public void onTestNewFunction() { + + } + + @Override + public void onSendWeather(WeatherSpec weatherSpec) { + + } + + @Override + public boolean useAutoConnect() { + return false; + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescDeviceSupport.java new file mode 100644 index 000000000..ca10ef14a --- /dev/null +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/vesc/VescDeviceSupport.java @@ -0,0 +1,179 @@ +/* Copyright (C) 2021 Daniel Dakhno + + 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 . */ +package nodomain.freeyourgadget.gadgetbridge.service.devices.vesc; + +import android.bluetooth.BluetoothGattCharacteristic; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; + +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.ByteBuffer; +import java.util.UUID; + +import nodomain.freeyourgadget.gadgetbridge.devices.vesc.VescCoordinator; +import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; +import nodomain.freeyourgadget.gadgetbridge.model.DeviceType; +import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder; +import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction; +import nodomain.freeyourgadget.gadgetbridge.util.CheckSums; + +public class VescDeviceSupport extends VescBaseDeviceSupport{ + BluetoothGattCharacteristic serialWriteCharacteristic; + + public static final String COMMAND_SET_RPM = "nodomain.freeyourgadget.gadgetbridge.vesc.command.SET_RPM"; + public static final String COMMAND_SET_CURRENT = "nodomain.freeyourgadget.gadgetbridge.vesc.command.SET_CURRENT"; + public static final String COMMAND_SET_BREAK_CURRENT = "nodomain.freeyourgadget.gadgetbridge.vesc.command.SET_BREAK_CURRENT"; + public static final String EXTRA_RPM = "EXTRA_RPM"; + public static final String EXTRA_CURRENT = "EXTRA_CURRENT"; + + private Logger logger = LoggerFactory.getLogger(getClass()); + + private DeviceType deviceType; + + public VescDeviceSupport(DeviceType type){ + super(); + logger.debug("VescDeviceSupport() {}", type); + + deviceType = type; + + if(type == DeviceType.VESC_NRF){ + addSupportedService(UUID.fromString(VescCoordinator.UUID_SERVICE_SERIAL_NRF)); + }else if(type == DeviceType.VESC_HM10){ + addSupportedService(UUID.fromString(VescCoordinator.UUID_SERVICE_SERIAL_HM10)); + } + } + + @Override + protected TransactionBuilder initializeDevice(TransactionBuilder builder) { + logger.debug("initializing device"); + + builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZING, getContext())); + + initBroadcast(); + + if(deviceType == DeviceType.VESC_NRF){ + this.serialWriteCharacteristic = getCharacteristic(UUID.fromString(VescCoordinator.UUID_CHARACTERISTIC_SERIAL_TX_NRF)); + }else if(deviceType == DeviceType.VESC_HM10){ + this.serialWriteCharacteristic = getCharacteristic(UUID.fromString(VescCoordinator.UUID_CHARACTERISTIC_SERIAL_TX_HM10)); + } + + return builder.add(new SetDeviceStateAction(getDevice(), GBDevice.State.INITIALIZED, getContext())); + } + + private void initBroadcast() { + LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(getContext()); + + IntentFilter filter = new IntentFilter(); + filter.addAction(COMMAND_SET_RPM); + filter.addAction(COMMAND_SET_CURRENT); + filter.addAction(COMMAND_SET_BREAK_CURRENT); + + broadcastManager.registerReceiver(commandReceiver, filter); + } + + BroadcastReceiver commandReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if(intent.getAction().equals(COMMAND_SET_RPM)){ + VescDeviceSupport.this.setRPM( + intent.getIntExtra(EXTRA_RPM, 0) + ); + }else if(intent.getAction().equals(COMMAND_SET_BREAK_CURRENT)){ + VescDeviceSupport.this.setBreakCurrent( + intent.getIntExtra(EXTRA_CURRENT, 0) + ); + }else if(intent.getAction().equals(COMMAND_SET_CURRENT)){ + VescDeviceSupport.this.setCurrent( + intent.getIntExtra(EXTRA_CURRENT, 0) + ); + } + } + }; + + public void setCurrent(int currentMillisAmperes){ + buildAndQueryPacket(CommandType.SET_CURRENT, currentMillisAmperes); + } + + public void setBreakCurrent(int breakCurrentMillisAmperes){ + buildAndQueryPacket(CommandType.SET_CURRENT_BRAKE, breakCurrentMillisAmperes); + } + + public void setRPM(int rpm){ + buildAndQueryPacket(CommandType.SET_RPM, rpm); + } + + public void buildAndQueryPacket(CommandType commandType, Object ... args){ + byte[] data = buildPacket(commandType, args); + queryPacket(data); + } + + public void queryPacket(byte[] data){ + new TransactionBuilder("write serial packet") + .write(this.serialWriteCharacteristic, data) + .queue(getQueue()); + } + + public byte[] buildPacket(CommandType commandType, Object ... args){ + int dataLength = 0; + for(Object arg : args){ + if(arg instanceof Integer) dataLength += 4; + else if(arg instanceof Short) dataLength += 2; + } + ByteBuffer buffer = ByteBuffer.allocate(dataLength); + + for(Object arg : args){ + if(arg instanceof Integer) buffer.putInt((Integer) arg); + if(arg instanceof Short) buffer.putShort((Short) arg); + } + + return buildPacket(commandType, buffer.array()); + } + + public byte[] buildPacket(CommandType commandType, byte[] data){ + return buildPacket(commandType.getCommandByte(), data); + } + + private byte[] buildPacket(byte commandByte, byte[] data){ + byte[] contents = new byte[data.length + 1]; + contents[0] = commandByte; + System.arraycopy(data, 0, contents, 1, data.length); + return buildPacket(contents); + } + + private byte[] buildPacket(byte[] contents){ + int dataLength = contents.length; + ByteBuffer buffer = ByteBuffer.allocate(dataLength + (dataLength < 256 ? 5 : 6)); + if(dataLength < 256){ + buffer.put((byte)0x02); + buffer.put((byte)dataLength); + }else{ + buffer.put((byte) 0x03); + buffer.putShort((short) dataLength); + } + buffer.put(contents); + buffer.putShort((short) CheckSums.getCRC16(contents, 0)); + buffer.put((byte) 0x03); + + return buffer.array(); + } +} diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java index abd6b8952..8c0baf1a5 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/DeviceHelper.java @@ -103,6 +103,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.nothing.Ear1Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.nut.NutCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.pinetime.PineTimeJFCoordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.qc35.QC35Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.QHybridCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi1Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.roidmi.Roidmi3Coordinator; @@ -110,6 +111,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.smaq2oss.SMAQ2OSSCoordinator import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12DeviceCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64Coordinator; import nodomain.freeyourgadget.gadgetbridge.devices.um25.Coordinator.UM25Coordinator; +import nodomain.freeyourgadget.gadgetbridge.devices.vesc.VescCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.vibratissimo.VibratissimoCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.waspos.WaspOSCoordinator; import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9DeviceCoordinator; @@ -315,8 +317,10 @@ public class DeviceHelper { result.add(new Ear1Coordinator()); result.add(new GalaxyBudsDeviceCoordinator()); result.add(new GalaxyBudsLiveDeviceCoordinator()); + result.add(new VescCoordinator()); result.add(new SonyWH1000XM3Coordinator()); result.add(new SonyWFSP800NCoordinator()); + result.add(new QC35Coordinator()); return result; } diff --git a/app/src/main/res/layout/activity_vesc_control.xml b/app/src/main/res/layout/activity_vesc_control.xml new file mode 100644 index 000000000..51e65c5e1 --- /dev/null +++ b/app/src/main/res/layout/activity_vesc_control.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + +