mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-03 17:02:13 +01:00
device-vesc (#2491)
Adds Support for BLDC controller VESC connected to a BLE serial device like an HM10. Reviewed-on: https://codeberg.org/Freeyourgadget/Gadgetbridge/pulls/2491 Co-authored-by: dakhnod <dakhnod@noreply.codeberg.org> Co-committed-by: dakhnod <dakhnod@noreply.codeberg.org>
This commit is contained in:
parent
e332f5d23a
commit
504b552f0c
@ -124,6 +124,10 @@
|
|||||||
android:name=".activities.CalBlacklistActivity"
|
android:name=".activities.CalBlacklistActivity"
|
||||||
android:label="@string/title_activity_calblacklist"
|
android:label="@string/title_activity_calblacklist"
|
||||||
android:parentActivityName=".activities.SettingsActivity" />
|
android:parentActivityName=".activities.SettingsActivity" />
|
||||||
|
<activity
|
||||||
|
android:name=".devices.vesc.VescControlActivity"
|
||||||
|
android:label="@string/devicetype_vesc"
|
||||||
|
android:parentActivityName=".activities.ControlCenterv2" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".activities.FwAppInstallerActivity"
|
android:name=".activities.FwAppInstallerActivity"
|
||||||
android:label="@string/title_activity_fw_app_insaller"
|
android:label="@string/title_activity_fw_app_insaller"
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -106,6 +106,8 @@ public enum DeviceType {
|
|||||||
SONY_WH_1000XM3(430, R.drawable.ic_device_headphones, R.drawable.ic_device_headphones_disabled, R.string.devicetype_sony_wh_1000xm3),
|
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),
|
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),
|
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);
|
TEST(1000, R.drawable.ic_device_default, R.drawable.ic_device_default_disabled, R.string.devicetype_test);
|
||||||
|
|
||||||
private final int key;
|
private final int key;
|
||||||
|
@ -93,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.sonyswr12.SonySWR12DeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.tlw64.TLW64Support;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.tlw64.TLW64Support;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.um25.Support.UM25Support;
|
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.vibratissimo.VibratissimoSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.waspos.WaspOSDeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.waspos.WaspOSDeviceSupport;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport;
|
import nodomain.freeyourgadget.gadgetbridge.service.devices.watch9.Watch9DeviceSupport;
|
||||||
@ -377,6 +378,10 @@ public class DeviceSupportFactory {
|
|||||||
case SONY_WF_SP800N:
|
case SONY_WF_SP800N:
|
||||||
deviceSupport = new ServiceDeviceSupport(new SonyHeadphonesSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
deviceSupport = new ServiceDeviceSupport(new SonyHeadphonesSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||||
break;
|
break;
|
||||||
|
case VESC_NRF:
|
||||||
|
case VESC_HM10:
|
||||||
|
deviceSupport = new ServiceDeviceSupport(new VescDeviceSupport(gbDevice.getType()), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||||
|
break;
|
||||||
case BOSE_QC35:
|
case BOSE_QC35:
|
||||||
deviceSupport = new ServiceDeviceSupport(new QC35BaseSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
deviceSupport = new ServiceDeviceSupport(new QC35BaseSupport(), EnumSet.of(ServiceDeviceSupport.Flags.BUSY_CHECKING));
|
||||||
break;
|
break;
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -111,6 +111,7 @@ import nodomain.freeyourgadget.gadgetbridge.devices.smaq2oss.SMAQ2OSSCoordinator
|
|||||||
import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12DeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.sonyswr12.SonySWR12DeviceCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64Coordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.tlw64.TLW64Coordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.um25.Coordinator.UM25Coordinator;
|
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.vibratissimo.VibratissimoCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.waspos.WaspOSCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.waspos.WaspOSCoordinator;
|
||||||
import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9DeviceCoordinator;
|
import nodomain.freeyourgadget.gadgetbridge.devices.watch9.Watch9DeviceCoordinator;
|
||||||
@ -316,6 +317,7 @@ public class DeviceHelper {
|
|||||||
result.add(new Ear1Coordinator());
|
result.add(new Ear1Coordinator());
|
||||||
result.add(new GalaxyBudsDeviceCoordinator());
|
result.add(new GalaxyBudsDeviceCoordinator());
|
||||||
result.add(new GalaxyBudsLiveDeviceCoordinator());
|
result.add(new GalaxyBudsLiveDeviceCoordinator());
|
||||||
|
result.add(new VescCoordinator());
|
||||||
result.add(new SonyWH1000XM3Coordinator());
|
result.add(new SonyWH1000XM3Coordinator());
|
||||||
result.add(new SonyWFSP800NCoordinator());
|
result.add(new SonyWFSP800NCoordinator());
|
||||||
result.add(new QC35Coordinator());
|
result.add(new QC35Coordinator());
|
||||||
|
76
app/src/main/res/layout/activity_vesc_control.xml
Normal file
76
app/src/main/res/layout/activity_vesc_control.xml
Normal 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>
|
@ -1442,6 +1442,6 @@
|
|||||||
<string name="watchface_dialog_widget_timeout_show_circle">Show circle on timeout:</string>
|
<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_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="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="devicetype_bose_qc35">Bose QC35</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in New Issue
Block a user