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 extends Activity> 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 extends ActivitySample> 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 extends Activity> 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 extends Activity> 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 extends ActivitySample> 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 extends Activity> 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 extends Alarm> 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 extends Alarm> 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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
index e9ff1c688..cc06590b5 100644
--- a/app/src/main/res/values-cs/strings.xml
+++ b/app/src/main/res/values-cs/strings.xml
@@ -988,7 +988,7 @@
Párování systémem Doprovodného Zařízení
Poloha musí být povolena
Stres
- Záznam cyklistiky
+ Cyklus
Dýchání
PineTime (JF Firmware)
TLW64
@@ -1496,4 +1496,19 @@
Min rytmus
Rozsahy
Převzorkování zvuku
+ Možnosti vyhledávání a párování
+ Nastavení zvuku okolí
+ Nastavení přehrávání
+ Ovládání hlasitosti
+ Vypnout
+ Vypnutí
+ Opravdu chcete zařízení vypnout\?
+ Přidat testovacího zařízení
+ Sony WF-SP800N
+ Vyhledávat nepodporovaná zařízení
+ Povolením této volby budou vyhledávána i nepodporovaná zařízení. Kliknutí zkopíruje jméno a MAC adresu zařízení do schránky. Podržení spustí dialog `Přidat testovací zařízení`.
+ Pozastavení přehrávání při sundání sluchátek
+ Režim tlačítka (vlevo)
+ Vypnout
+ Režim tlačítka (vpravo)
\ No newline at end of file
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index e2c453f67..8d4ab0e76 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -983,10 +983,10 @@
Kalorien
Aktiv
Schritte
- Minimum
- Maximum
- Bergab
- Bergauf
+ Minimale Höhe
+ Maximale Höhe
+ Höhenverlust
+ Höhengewinn
Strecke
Detail der Sportaktivität
Maximum
@@ -1034,8 +1034,8 @@
Züge
Flach
Durchschnittliche Schläge
- Durchschnittliche Schlag Distanz
- Durchschnittsfortschritt
+ Durchschnittliche Schlagdistanz
+ Mittlere Schrittlänge
Gesamtfortschritt
Beschriftung bearbeiten
Swolf-Index
@@ -1491,6 +1491,33 @@
Strecke basiert auf Schrittzahl und Schrittlänge (siehe Einstellungen - Über Dich)
Angeregt
Batteriefach
+ Umgebungsgeräusch-Steuerung
+ Minimum
+ Max Kadenz
+ Min Kadenz
+ Aus
+ Auffindungs- und Kopplungsoptionen
+ Testgerät hinzufügen
+ Sony WF-SP800N
+ Ausschalten
+ Ausschalten
+ Sind Sie sicher, dass Sie das Gerät abschalten wollen\?
+ Nicht unterstützte Geräte finden
+ Min Schrittlänge
+ Das aktivieren dieser Option erlaubt das Anzeigen aller Bluetooth Geräte die während eines Scans gefunden werden. Ein kurzes Tippen kopiert Gerätename und Mac-adresse in die Zwischenablage. Langes Drücken öffnet den \'Neues Gerät hinzufügen\' Dialog.
+ Mittlere Höhe
+ Max Herzfrequenz
+ Min Herzfrequenz
+ Pausieren wenn Kopfhörer abgenommen werden
+ Max Schrittlänge
+ Mittlere Kadenz
+ Schritte/Min
+ Schalter Modus (Links)
+ Wiedergabesteuerung
+ Schalter Modus (Rechts)
+ Lautstärkeregelung
+ Bänder
+ Audio Upsampling
%d Geräte verbunden
Keine Geräte verbunden
\ No newline at end of file
diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml
index 7a2f0f9ce..513a7a421 100644
--- a/app/src/main/res/values-en-rGB/strings.xml
+++ b/app/src/main/res/values-en-rGB/strings.xml
@@ -1490,4 +1490,21 @@
Min Heartrate
Max Stride
Min Stride
+ Power Off
+ Pause when headphones are taken off
+ Ambient Sound Control
+ Discovery and Pairing options
+ Power Off
+ Add test device
+ Sony WF-SP800N
+ Are you sure you want to power off the device\?
+ Button Mode (Left)
+ Discover unsupported devices
+ Volume Control
+ Enabling this option will display all discovered Bluetooth devices when scanning. Short tap will copy device name and MAC address to the clipboard. Long press will launch `Add test device` dialog.
+ Button Mode (Right)
+ Off
+ Playback Control
+ Bands
+ Audio Upsampling
\ No newline at end of file
diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml
index a9ae082d5..32d13f366 100644
--- a/app/src/main/res/values-he/strings.xml
+++ b/app/src/main/res/values-he/strings.xml
@@ -1494,4 +1494,20 @@
צעד מזערי
צמידים
Upsampling לשמע
+ לכבות את ההתקן\?
+ בקרת שמע
+ אפשרויות גילוי וצימוד
+ מצב הכפתור (ימין)
+ גילוי התקנים שאינם נתמכים
+ כיבוי
+ כיבוי
+ הוספת התקן בדיקה
+ מצב כפתור (שמאל)
+ כבוי
+ Sony WF-SP800N
+ הפעלת אפשרות זו תציג התקני Bluetooth בעת הסריקה. נגיעה קצרה תעתיק את שם ההתקן ואת כתובת החומרה שלו ללוח הגזירים. לחיצה ארוכה תפעיל את חלונית `הוספת התקן בדיקה`.
+ להשהות כאשר האוזניות מוסרות
+ בקרת שמע אופף
+ בקרת נגינה
+ Bose QC35
\ No newline at end of file
diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
index 9a9d68380..3be5d5dd0 100644
--- a/app/src/main/res/values-pt/strings.xml
+++ b/app/src/main/res/values-pt/strings.xml
@@ -6,14 +6,14 @@
Depurar
Sair
Sincronizar
- Procurar aparelho perdido
+ Encontrar dispositivo perdido
Captura de ecrã
Mudar cor do LED
Mudar frequência FM
Desconectar
Apagar aparelho
Apagar %1$s
- Isto irá apagar o aparelho e apagar todos os dados associados!
+ Isto irá apagar o dispositivo e todos os dados associados!
Abrir gaveta de navegação
Fechar gaveta de navegação
Pressione longo no cartão para desconectar
@@ -123,7 +123,7 @@
Tente obter a localização online, use dados armazenados como alternativa
Por favor, ative a localização de rede
localização obtida
- Forçar o protocolo de notificação
+ Forçar protocolo de notificação
Esta opção força o uso do protocolo de notificação mais recente dependendo da versão do firmware. FAÇA ISSO APENAS SE SOUBER O QUE ESTÁ FAZENDO!
Permitir recursos não certificados
Ativar recursos não testados. FAÇA ISSO APENAS SE SOUBER O QUE ESTÁ FAZENDO!
@@ -543,7 +543,7 @@
Ative isto se o seu aparelho pode mostrar idiomas que se escrevem da direita para a esquerda
Comprimento máximo da linha da direita para a esquerda
Alonga ou encurta as linhas da direita para a esquerda em que o texto é cortado
- Envia eventos do calendário para linha do tempo
+ Enviar eventos do calendário para a linha do tempo
Ativar JS em segundo plano
Quando ativado, permite que as mostradores mostrem o clima, informações da bateria, etc.
Medição diária dos batimentos cardíacos
@@ -1446,4 +1446,7 @@
Direita
2º fuso horário
Minutos ativos
+ Tem a certeza que pretende desligar o dispositivo\?
+ Desligar
+ Desligar
\ No newline at end of file
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 3e4b0b193..0cfb8d0f7 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -460,8 +460,8 @@
Когда вы мало двигаетесь, браслет время от времени вибрирует
Период низкой активности (в минутах)
Отключить уведомления о низкой активности во время режима \"Не беспокоить\"
- Начало режим \"Не беспокоить\"
- Окончание режима \"Не беспокоить\"
+ Начальное время
+ Время окончания
Автоматически
Упрощённый китайский язык
Традиционный китайский язык
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index aaa193231..096ca9e50 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -1521,4 +1521,19 @@
adım/dak
Bant
Ses Üst Örnekleme
+ Düğme Modu (Sağ)
+ Oynatım Denetimi
+ Bu seçeneğin etkinleştirilmesi, tarama sırasında keşfedilen tüm bluetooth aygıtlarını görüntüleyecektir. Kısa dokunma, aygıt adını ve MAC adresini panoya kopyalayacaktır. Uzun basıldığında, `Test aygıtı ekle` iletişim kutusu açılacaktır.
+ Aygıtı kapatmak istediğinizden emin misiniz\?
+ Kapat
+ Sony WF-SP800N
+ Kapat
+ Keşif ve Eşleştirme seçenekleri
+ Test aygıtı ekle
+ Desteklenmeyen aygıtları keşfet
+ Kapalı
+ Kulaklıklar çıkarıldığında duraklat
+ Düğme Modu (Sol)
+ Ortam Sesi Denetimi
+ Ses Denetimi
\ No newline at end of file
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 204a09edb..a0d0343f2 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -1505,4 +1505,15 @@
Додати тестовий пристрій
Виявляти непідтримувані пристрої
Якщо ввімкнути цей параметр, під час сканування буде показано всі виявлені Bluetooth-пристрої. Короткий дотик призведе до копіювання імені пристрою та mac-адреси до буфера обміну. Тривале натискання відкриє діалогове вікно «Додати тестовий пристрій».
+ Режим кнопки (Ліворуч)
+ Sony WF-SP800N
+ Регулювання гучності
+ Ви впевнені, що хочете вимкнути пристрій\?
+ Вимкнути
+ Вимкнути
+ Зупиняти, коли знімаються навушники
+ Вимкнути
+ Режим кнопки (Праворуч)
+ Керування навколишнім звуком
+ Керування відтворенням
\ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 9164d268e..8a8a2359c 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -1500,4 +1500,19 @@
步数/分钟
乐队
音频提升采样
+ 关机
+ 关机
+ 您确定想要关闭此设备的电源吗?
+ 索尼 WF-SP800N
+ 发现和配对选项
+ 添加测试设备
+ 发现未支持的设备
+ 启用该选项将会显示扫描时所有已发现的设备。短按将会复制设备名称和MAC地址到剪切板。长按将会显示“添加测试设备”对话框。
+ 关
+ 当摘下耳机时暂停
+ 按钮模式(左)
+ 按钮模式(右)
+ 环境音控制
+ 回放控制
+ 音量控制
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index c2f5d9d5f..361656448 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1120,6 +1120,8 @@
Distance
Uphill
Downhill
+ Uphill distance
+ Downhill distance
Maximum
Minimum
Average
@@ -1442,6 +1444,8 @@
Show circle on timeout:
Enable on-device pairing confirmation
On-device pairing confirmations can get annoying. Disabling them might lose you functionality.
+ VESC
+ Bose QC35
no devices connected
%d devices connected
-
\ No newline at end of file
+
diff --git a/app/src/main/res/xml/devicesettings_qc35.xml b/app/src/main/res/xml/devicesettings_qc35.xml
new file mode 100644
index 000000000..4d1899c10
--- /dev/null
+++ b/app/src/main/res/xml/devicesettings_qc35.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java
index 360c26027..aa71841db 100644
--- a/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java
+++ b/app/src/test/java/nodomain/freeyourgadget/gadgetbridge/service/TestDeviceSupport.java
@@ -15,6 +15,7 @@ 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.Reminder;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
class TestDeviceSupport extends AbstractDeviceSupport {
@@ -64,6 +65,11 @@ class TestDeviceSupport extends AbstractDeviceSupport {
}
+ @Override
+ public void onSetReminders(ArrayList extends Reminder> reminders) {
+
+ }
+
@Override
public void onSetCallState(CallSpec callSpec) {
@@ -203,4 +209,9 @@ class TestDeviceSupport extends AbstractDeviceSupport {
public void onSetLedColor(int color) {
}
+
+ @Override
+ public void onPowerOff() {
+
+ }
}