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:
dakhnod 2021-12-27 15:47:10 +01:00 committed by Andreas Shimokawa
parent e332f5d23a
commit 504b552f0c
11 changed files with 905 additions and 2 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

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

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

@ -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.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;
@ -377,6 +378,10 @@ 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;

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

@ -111,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;
@ -316,6 +317,7 @@ 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());

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

@ -1442,6 +1442,6 @@
<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>
</resources>
</resources>