Merge branch 'master' of codeberg.org:Freeyourgadget/Gadgetbridge into multi-device-support

This commit is contained in:
Daniel Dakhno 2021-12-27 17:22:47 +01:00
commit dc7bdb3e3d
31 changed files with 1488 additions and 23 deletions

View File

@ -124,6 +124,10 @@
android:name=".activities.CalBlacklistActivity"
android:label="@string/title_activity_calblacklist"
android:parentActivityName=".activities.SettingsActivity" />
<activity
android:name=".devices.vesc.VescControlActivity"
android:label="@string/devicetype_vesc"
android:parentActivityName=".activities.ControlCenterv2" />
<activity
android:name=".activities.FwAppInstallerActivity"
android:label="@string/title_activity_fw_app_insaller"

View File

@ -133,6 +133,8 @@ public class DeviceSettingsPreferenceConst {
public static final String PREF_SONY_AUTOMATIC_POWER_OFF = "pref_sony_automatic_power_off";
public static final String PREF_SONY_NOTIFICATION_VOICE_GUIDE = "pref_sony_notification_voice_guide";
public static final String PREF_QC35_NOISE_CANCELLING_LEVEL = "qc35_noise_cancelling_level";
public static final String PREFS_ACTIVITY_IN_DEVICE_CARD = "prefs_activity_in_device_card";
public static final String PREFS_ACTIVITY_IN_DEVICE_CARD_STEPS = "prefs_activity_in_device_card_steps";
public static final String PREFS_ACTIVITY_IN_DEVICE_CARD_SLEEP = "prefs_activity_in_device_card_sleep";

View File

@ -152,6 +152,7 @@ import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.Dev
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREFS_ACTIVITY_IN_DEVICE_CARD_SLEEP;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREFS_ACTIVITY_IN_DEVICE_CARD_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREFS_DEVICE_CHARTS_TABS;
import static nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst.PREF_QC35_NOISE_CANCELLING_LEVEL;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_ACTIVATE_DISPLAY_ON_LIFT;
import static nodomain.freeyourgadget.gadgetbridge.devices.huami.HuamiConst.PREF_DEVICE_ACTION_FELL_SLEEP_BROADCAST;
@ -569,6 +570,8 @@ public class DeviceSpecificSettingsFragment extends PreferenceFragmentCompat imp
addPreferenceHandlerFor(PREF_SONY_AUTOMATIC_POWER_OFF);
addPreferenceHandlerFor(PREF_SONY_NOTIFICATION_VOICE_GUIDE);
addPreferenceHandlerFor(PREF_QC35_NOISE_CANCELLING_LEVEL);
String sleepTimeState = prefs.getString(PREF_SLEEP_TIME, PREF_DO_NOT_DISTURB_OFF);
boolean sleepTimeScheduled = sleepTimeState.equals(PREF_DO_NOT_DISTURB_SCHEDULED);

View File

@ -93,6 +93,9 @@ public class HuamiActivitySummaryParser implements ActivitySummaryParser {
float caloriesBurnt;
float distanceMeters;
float distanceMeters2 = 0;
float ascentDistance = 0;
float descentDistance = 0;
float flatDistance = 0;
float ascentMeters = 0;
float descentMeters = 0;
float maxAltitude = 0;
@ -183,11 +186,11 @@ public class HuamiActivitySummaryParser implements ActivitySummaryParser {
// for cycling it seems to work... hmm...
// 28 bytes
buffer.getInt(); // unknown
buffer.getInt(); // unknown
ascentDistance = buffer.getFloat();
ascentSeconds = buffer.getInt() / 1000; //ms?
buffer.getInt(); // unknown;
descentDistance = buffer.getFloat();
descentSeconds = buffer.getInt() / 1000; //ms?
buffer.getInt(); // unknown;
flatDistance = buffer.getFloat();
flatSeconds = buffer.getInt() / 1000; // ms?
} else if (activityKind == ActivityKind.TYPE_SWIMMING || activityKind == ActivityKind.TYPE_SWIMMING_OPENWATER) {
// offset 0x8c
@ -253,11 +256,11 @@ public class HuamiActivitySummaryParser implements ActivitySummaryParser {
} else {
// 28 bytes
buffer.getInt(); // unknown
buffer.getInt(); // unknown
buffer.getInt(); // unknown probably ascentDistance = buffer.getFloat();
ascentSeconds = buffer.getInt() / 1000; //ms?
buffer.getInt(); // unknown;
buffer.getInt(); // unknown probably descentDistance = buffer.getFloat();
descentSeconds = buffer.getInt() / 1000; //ms?
buffer.getInt(); // unknown;
buffer.getInt(); // unknown probably flatDistance = buffer.getFloat();
flatSeconds = buffer.getInt() / 1000; // ms?
addSummaryData("ascentSeconds", ascentSeconds, "seconds");
@ -298,6 +301,9 @@ public class HuamiActivitySummaryParser implements ActivitySummaryParser {
addSummaryData("ascentSeconds", ascentSeconds, "seconds");
addSummaryData("descentSeconds", descentSeconds, "seconds");
addSummaryData("flatSeconds", flatSeconds, "seconds");
addSummaryData("ascentDistance", ascentDistance, "meters");
addSummaryData("descentDistance", descentDistance, "meters");
addSummaryData("flatDistance", flatDistance, "meters");
addSummaryData("distanceMeters", distanceMeters, "meters");
// addSummaryData("distanceMeters2", distanceMeters2, "meters");

View File

@ -0,0 +1,145 @@
/* 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 <http://www.gnu.org/licenses/>. */
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. */
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);
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. */
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;
}
}

View File

@ -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'], " +

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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 <http://www.gnu.org/licenses/>. */
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());
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. */
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. */
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<GBDeviceEvent> 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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. */
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. */
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;
}
}

View File

@ -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 <http://www.gnu.org/licenses/>. */
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();
}
}

View File

@ -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;
}

View File

@ -0,0 +1,76 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RPM: "
android:labelFor="@+id/vesc_control_input_rpm"/>
<EditText
android:layout_width="100dp"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:id="@+id/vesc_control_input_rpm"/>
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Break current (A): " />
<EditText
android:layout_width="100dp"
android:layout_height="wrap_content"
android:inputType="numberDecimal"
android:id="@+id/vesc_control_input_break_current"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<Button
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_weight="0.5"
android:text="break"
android:id="@+id/vesc_control_button_break"/>
<Button
android:layout_width="wrap_content"
android:layout_height="100dp"
android:layout_weight="0.5"
android:text="fwd"
android:id="@+id/vesc_control_button_fwd" />
</LinearLayout>
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Volume keys control"
android:id="@+id/vesc_control_checkbox_volume_keys" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -988,7 +988,7 @@
<string name="companiondevice_pairing">Párování systémem Doprovodného Zařízení</string>
<string name="require_location_provider">Poloha musí být povolena</string>
<string name="menuitem_stress">Stres</string>
<string name="menuitem_cycles">Záznam cyklistiky</string>
<string name="menuitem_cycles">Cyklus</string>
<string name="menuitem_breathing">Dýchání</string>
<string name="devicetype_pinetime_jf">PineTime (JF Firmware)</string>
<string name="devicetype_tlw64">TLW64</string>
@ -1496,4 +1496,19 @@
<string name="minCadence">Min rytmus</string>
<string name="pref_header_sony_equalizer_bands">Rozsahy</string>
<string name="sony_audio_upsampling">Převzorkování zvuku</string>
<string name="activity_prefs_discovery_pairing">Možnosti vyhledávání a párování</string>
<string name="sony_button_mode_ambient_sound_control">Nastavení zvuku okolí</string>
<string name="sony_button_mode_playback_control">Nastavení přehrávání</string>
<string name="sony_button_mode_volume_control">Ovládání hlasitosti</string>
<string name="controlcenter_power_off_confirm_title">Vypnout</string>
<string name="controlcenter_power_off">Vypnutí</string>
<string name="controlcenter_power_off_confirm_description">Opravdu chcete zařízení vypnout\?</string>
<string name="add_test_device">Přidat testovacího zařízení</string>
<string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
<string name="discover_unsupported_devices">Vyhledávat nepodporovaná zařízení</string>
<string name="discover_unsupported_devices_description">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í`.</string>
<string name="sony_pause_when_taken_off">Pozastavení přehrávání při sundání sluchátek</string>
<string name="sony_button_mode_left">Režim tlačítka (vlevo)</string>
<string name="sony_button_mode_off">Vypnout</string>
<string name="sony_button_mode_right">Režim tlačítka (vpravo)</string>
</resources>

View File

@ -983,10 +983,10 @@
<string name="caloriesBurnt">Kalorien</string>
<string name="activeSeconds">Aktiv</string>
<string name="steps">Schritte</string>
<string name="minAltitude">Minimum</string>
<string name="maxAltitude">Maximum</string>
<string name="descentMeters">Bergab</string>
<string name="ascentMeters">Bergauf</string>
<string name="minAltitude">Minimale Höhe</string>
<string name="maxAltitude">Maximale Höhe</string>
<string name="descentMeters">Höhenverlust</string>
<string name="ascentMeters">Höhengewinn</string>
<string name="distanceMeters">Strecke</string>
<string name="activity_summary_detail">Detail der Sportaktivität</string>
<string name="maxSpeed">Maximum</string>
@ -1034,8 +1034,8 @@
<string name="strokes_unit">Züge</string>
<string name="flatSeconds">Flach</string>
<string name="averageStrokesPerSecond">Durchschnittliche Schläge</string>
<string name="averageStrokeDistance">Durchschnittliche Schlag Distanz</string>
<string name="averageStride">Durchschnittsfortschritt</string>
<string name="averageStrokeDistance">Durchschnittliche Schlagdistanz</string>
<string name="averageStride">Mittlere Schrittlänge</string>
<string name="totalStride">Gesamtfortschritt</string>
<string name="activity_summary_edit_name_title">Beschriftung bearbeiten</string>
<string name="swolf_index">Swolf-Index</string>
@ -1491,6 +1491,33 @@
<string name="prefs_activity_in_device_card_distance_title_summary">Strecke basiert auf Schrittzahl und Schrittlänge (siehe Einstellungen - Über Dich)</string>
<string name="sony_equalizer_preset_excited">Angeregt</string>
<string name="battery_case">Batteriefach</string>
<string name="sony_button_mode_ambient_sound_control">Umgebungsgeräusch-Steuerung</string>
<string name="minSpeed">Minimum</string>
<string name="maxCadence">Max Kadenz</string>
<string name="minCadence">Min Kadenz</string>
<string name="sony_button_mode_off">Aus</string>
<string name="activity_prefs_discovery_pairing">Auffindungs- und Kopplungsoptionen</string>
<string name="add_test_device">Testgerät hinzufügen</string>
<string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
<string name="controlcenter_power_off_confirm_title">Ausschalten</string>
<string name="controlcenter_power_off">Ausschalten</string>
<string name="controlcenter_power_off_confirm_description">Sind Sie sicher, dass Sie das Gerät abschalten wollen\?</string>
<string name="discover_unsupported_devices">Nicht unterstützte Geräte finden</string>
<string name="minStride">Min Schrittlänge</string>
<string name="discover_unsupported_devices_description">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.</string>
<string name="averageAltitude">Mittlere Höhe</string>
<string name="maxHR">Max Herzfrequenz</string>
<string name="minHR">Min Herzfrequenz</string>
<string name="sony_pause_when_taken_off">Pausieren wenn Kopfhörer abgenommen werden</string>
<string name="maxStride">Max Schrittlänge</string>
<string name="averageCadence">Mittlere Kadenz</string>
<string name="spm">Schritte/Min</string>
<string name="sony_button_mode_left">Schalter Modus (Links)</string>
<string name="sony_button_mode_playback_control">Wiedergabesteuerung</string>
<string name="sony_button_mode_right">Schalter Modus (Rechts)</string>
<string name="sony_button_mode_volume_control">Lautstärkeregelung</string>
<string name="pref_header_sony_equalizer_bands">Bänder</string>
<string name="sony_audio_upsampling">Audio Upsampling</string>
<string name="info_connected_count">%d Geräte verbunden</string>
<string name="info_no_devices_connected">Keine Geräte verbunden</string>
</resources>

View File

@ -1490,4 +1490,21 @@
<string name="minHR">Min Heartrate</string>
<string name="maxStride">Max Stride</string>
<string name="minStride">Min Stride</string>
<string name="controlcenter_power_off">Power Off</string>
<string name="sony_pause_when_taken_off">Pause when headphones are taken off</string>
<string name="sony_button_mode_ambient_sound_control">Ambient Sound Control</string>
<string name="activity_prefs_discovery_pairing">Discovery and Pairing options</string>
<string name="controlcenter_power_off_confirm_title">Power Off</string>
<string name="add_test_device">Add test device</string>
<string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
<string name="controlcenter_power_off_confirm_description">Are you sure you want to power off the device\?</string>
<string name="sony_button_mode_left">Button Mode (Left)</string>
<string name="discover_unsupported_devices">Discover unsupported devices</string>
<string name="sony_button_mode_volume_control">Volume Control</string>
<string name="discover_unsupported_devices_description">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.</string>
<string name="sony_button_mode_right">Button Mode (Right)</string>
<string name="sony_button_mode_off">Off</string>
<string name="sony_button_mode_playback_control">Playback Control</string>
<string name="pref_header_sony_equalizer_bands">Bands</string>
<string name="sony_audio_upsampling">Audio Upsampling</string>
</resources>

View File

@ -1494,4 +1494,20 @@
<string name="minStride">צעד מזערי</string>
<string name="pref_header_sony_equalizer_bands">צמידים</string>
<string name="sony_audio_upsampling">Upsampling לשמע</string>
<string name="controlcenter_power_off_confirm_description">לכבות את ההתקן\?</string>
<string name="sony_button_mode_volume_control">בקרת שמע</string>
<string name="activity_prefs_discovery_pairing">אפשרויות גילוי וצימוד</string>
<string name="sony_button_mode_right">מצב הכפתור (ימין)</string>
<string name="discover_unsupported_devices">גילוי התקנים שאינם נתמכים</string>
<string name="controlcenter_power_off">כיבוי</string>
<string name="controlcenter_power_off_confirm_title">כיבוי</string>
<string name="add_test_device">הוספת התקן בדיקה</string>
<string name="sony_button_mode_left">מצב כפתור (שמאל)</string>
<string name="sony_button_mode_off">כבוי</string>
<string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
<string name="discover_unsupported_devices_description">הפעלת אפשרות זו תציג התקני Bluetooth בעת הסריקה. נגיעה קצרה תעתיק את שם ההתקן ואת כתובת החומרה שלו ללוח הגזירים. לחיצה ארוכה תפעיל את חלונית `הוספת התקן בדיקה`.</string>
<string name="sony_pause_when_taken_off">להשהות כאשר האוזניות מוסרות</string>
<string name="sony_button_mode_ambient_sound_control">בקרת שמע אופף</string>
<string name="sony_button_mode_playback_control">בקרת נגינה</string>
<string name="devicetype_bose_qc35">Bose QC35</string>
</resources>

View File

@ -6,14 +6,14 @@
<string name="action_debug">Depurar</string>
<string name="action_quit">Sair</string>
<string name="controlcenter_fetch_activity_data">Sincronizar</string>
<string name="controlcenter_find_device">Procurar aparelho perdido</string>
<string name="controlcenter_find_device">Encontrar dispositivo perdido</string>
<string name="controlcenter_take_screenshot">Captura de ecrã</string>
<string name="controlcenter_change_led_color">Mudar cor do LED</string>
<string name="controlcenter_change_fm_frequency">Mudar frequência FM</string>
<string name="controlcenter_disconnect">Desconectar</string>
<string name="controlcenter_delete_device">Apagar aparelho</string>
<string name="controlcenter_delete_device_name">Apagar %1$s</string>
<string name="controlcenter_delete_device_dialogmessage">Isto irá apagar o aparelho e apagar todos os dados associados!</string>
<string name="controlcenter_delete_device_dialogmessage">Isto irá apagar o dispositivo e todos os dados associados!</string>
<string name="controlcenter_navigation_drawer_open">Abrir gaveta de navegação</string>
<string name="controlcenter_navigation_drawer_close">Fechar gaveta de navegação</string>
<string name="controlcenter_snackbar_need_longpress">Pressione longo no cartão para desconectar</string>
@ -123,7 +123,7 @@
<string name="pref_summary_location_keep_uptodate">Tente obter a localização online, use dados armazenados como alternativa</string>
<string name="toast_enable_networklocationprovider">Por favor, ative a localização de rede</string>
<string name="toast_aqurired_networklocation">localização obtida</string>
<string name="pref_title_pebble_forceprotocol">Forçar o protocolo de notificação</string>
<string name="pref_title_pebble_forceprotocol">Forçar protocolo de notificação</string>
<string name="pref_summary_pebble_forceprotocol">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!</string>
<string name="pref_title_pebble_forceuntested">Permitir recursos não certificados</string>
<string name="pref_summary_pebble_forceuntested">Ativar recursos não testados. FAÇA ISSO APENAS SE SOUBER O QUE ESTÁ FAZENDO!</string>
@ -543,7 +543,7 @@
<string name="pref_summary_rtl">Ative isto se o seu aparelho pode mostrar idiomas que se escrevem da direita para a esquerda</string>
<string name="pref_rtl_max_line_length">Comprimento máximo da linha da direita para a esquerda</string>
<string name="pref_rtl_max_line_length_summary">Alonga ou encurta as linhas da direita para a esquerda em que o texto é cortado</string>
<string name="pref_summary_enable_calendar_sync">Envia eventos do calendário para linha do tempo</string>
<string name="pref_summary_enable_calendar_sync">Enviar eventos do calendário para a linha do tempo</string>
<string name="pref_title_pebble_enable_bgjs">Ativar JS em segundo plano</string>
<string name="pref_summary_pebble_enable_bgjs">Quando ativado, permite que as mostradores mostrem o clima, informações da bateria, etc.</string>
<string name="prefs_title_heartrate_measurement_interval">Medição diária dos batimentos cardíacos</string>
@ -1446,4 +1446,7 @@
<string name="watchface_dialog_widget_preset_right">Direita</string>
<string name="watchface_widget_type_2nd_tz">2º fuso horário</string>
<string name="watchface_widget_type_active_mins">Minutos ativos</string>
<string name="controlcenter_power_off_confirm_description">Tem a certeza que pretende desligar o dispositivo\?</string>
<string name="controlcenter_power_off">Desligar</string>
<string name="controlcenter_power_off_confirm_title">Desligar</string>
</resources>

View File

@ -460,8 +460,8 @@
<string name="mi2_prefs_inactivity_warnings_summary">Когда вы мало двигаетесь, браслет время от времени вибрирует</string>
<string name="mi2_prefs_inactivity_warnings_threshold">Период низкой активности (в минутах)</string>
<string name="mi2_prefs_inactivity_warnings_dnd_summary">Отключить уведомления о низкой активности во время режима \"Не беспокоить\"</string>
<string name="mi2_prefs_do_not_disturb_start">Начало режим \"Не беспокоить\"</string>
<string name="mi2_prefs_do_not_disturb_end">Окончание режима \"Не беспокоить\"</string>
<string name="mi2_prefs_do_not_disturb_start">Начальное время</string>
<string name="mi2_prefs_do_not_disturb_end">Время окончания</string>
<string name="automatic">Автоматически</string>
<string name="simplified_chinese">Упрощённый китайский язык</string>
<string name="traditional_chinese">Традиционный китайский язык</string>

View File

@ -1521,4 +1521,19 @@
<string name="spm">adım/dak</string>
<string name="pref_header_sony_equalizer_bands">Bant</string>
<string name="sony_audio_upsampling">Ses Üst Örnekleme</string>
<string name="sony_button_mode_right">Düğme Modu (Sağ)</string>
<string name="sony_button_mode_playback_control">Oynatım Denetimi</string>
<string name="discover_unsupported_devices_description">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.</string>
<string name="controlcenter_power_off_confirm_description">Aygıtı kapatmak istediğinizden emin misiniz\?</string>
<string name="controlcenter_power_off_confirm_title">Kapat</string>
<string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
<string name="controlcenter_power_off">Kapat</string>
<string name="activity_prefs_discovery_pairing">Keşif ve Eşleştirme seçenekleri</string>
<string name="add_test_device">Test aygıtı ekle</string>
<string name="discover_unsupported_devices">Desteklenmeyen aygıtları keşfet</string>
<string name="sony_button_mode_off">Kapalı</string>
<string name="sony_pause_when_taken_off">Kulaklıklar çıkarıldığında duraklat</string>
<string name="sony_button_mode_left">Düğme Modu (Sol)</string>
<string name="sony_button_mode_ambient_sound_control">Ortam Sesi Denetimi</string>
<string name="sony_button_mode_volume_control">Ses Denetimi</string>
</resources>

View File

@ -1505,4 +1505,15 @@
<string name="add_test_device">Додати тестовий пристрій</string>
<string name="discover_unsupported_devices">Виявляти непідтримувані пристрої</string>
<string name="discover_unsupported_devices_description">Якщо ввімкнути цей параметр, під час сканування буде показано всі виявлені Bluetooth-пристрої. Короткий дотик призведе до копіювання імені пристрою та mac-адреси до буфера обміну. Тривале натискання відкриє діалогове вікно «Додати тестовий пристрій».</string>
<string name="sony_button_mode_left">Режим кнопки (Ліворуч)</string>
<string name="devicetype_sony_wf_sp800n">Sony WF-SP800N</string>
<string name="sony_button_mode_volume_control">Регулювання гучності</string>
<string name="controlcenter_power_off_confirm_description">Ви впевнені, що хочете вимкнути пристрій\?</string>
<string name="controlcenter_power_off">Вимкнути</string>
<string name="controlcenter_power_off_confirm_title">Вимкнути</string>
<string name="sony_pause_when_taken_off">Зупиняти, коли знімаються навушники</string>
<string name="sony_button_mode_off">Вимкнути</string>
<string name="sony_button_mode_right">Режим кнопки (Праворуч)</string>
<string name="sony_button_mode_ambient_sound_control">Керування навколишнім звуком</string>
<string name="sony_button_mode_playback_control">Керування відтворенням</string>
</resources>

View File

@ -1500,4 +1500,19 @@
<string name="spm">步数/分钟</string>
<string name="pref_header_sony_equalizer_bands">乐队</string>
<string name="sony_audio_upsampling">音频提升采样</string>
<string name="controlcenter_power_off_confirm_title">关机</string>
<string name="controlcenter_power_off">关机</string>
<string name="controlcenter_power_off_confirm_description">您确定想要关闭此设备的电源吗?</string>
<string name="devicetype_sony_wf_sp800n">索尼 WF-SP800N</string>
<string name="activity_prefs_discovery_pairing">发现和配对选项</string>
<string name="add_test_device">添加测试设备</string>
<string name="discover_unsupported_devices">发现未支持的设备</string>
<string name="discover_unsupported_devices_description">启用该选项将会显示扫描时所有已发现的设备。短按将会复制设备名称和MAC地址到剪切板。长按将会显示“添加测试设备”对话框。</string>
<string name="sony_button_mode_off"></string>
<string name="sony_pause_when_taken_off">当摘下耳机时暂停</string>
<string name="sony_button_mode_left">按钮模式(左)</string>
<string name="sony_button_mode_right">按钮模式(右)</string>
<string name="sony_button_mode_ambient_sound_control">环境音控制</string>
<string name="sony_button_mode_playback_control">回放控制</string>
<string name="sony_button_mode_volume_control">音量控制</string>
</resources>

View File

@ -1120,6 +1120,8 @@
<string name="distanceMeters">Distance</string>
<string name="ascentMeters">Uphill</string>
<string name="descentMeters">Downhill</string>
<string name="ascentDistance">Uphill distance</string>
<string name="descentDistance">Downhill distance</string>
<string name="maxAltitude">Maximum</string>
<string name="minAltitude">Minimum</string>
<string name="averageAltitude">Average</string>
@ -1442,6 +1444,8 @@
<string name="watchface_dialog_widget_timeout_show_circle">Show circle on timeout:</string>
<string name="qhybrid_title_on_device_confirmation">Enable on-device pairing confirmation</string>
<string name="qhybrid_summary_on_device_confirmation">On-device pairing confirmations can get annoying. Disabling them might lose you functionality.</string>
<string name="devicetype_vesc">VESC</string>
<string name="devicetype_bose_qc35">Bose QC35</string>
<string name="info_no_devices_connected">no devices connected</string>
<string name="info_connected_count">%d devices connected</string>
</resources>
</resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<SeekBarPreference
android:title="Noice cancelling level"
android:max="2"
android:key="qc35_noise_cancelling_level" />
</PreferenceScreen>

View File

@ -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() {
}
}