Pebble: Experimental support for BLE on all models via dev option in Pebble Settings

HOWTO:
1) Pair you normal Pebble (not necessary if already done), make sure it was connected once
2) Unpair your LE pebble if already paired
3) Switch on "Always prefer BLE" in Pebble Settings
4) Tap on the + in Control Center to add a new device
5) Pair your Pebble-LE XXXX or Pebble Time LE XXXX inside Gadgetbridge's Device Discovery actibity

Now Gadgetbridge will connect to your LE Pebble when tapping on Pebble XXXX if "Always Prefer BLE" option is enabled.
You can easily switch back to classic LE by turning that option off again
here
Andreas Shimokawa 2016-11-27 09:49:28 +01:00
parent 2f7eb9ef23
commit 34ad088b88
10 changed files with 144 additions and 44 deletions

View File

@ -40,13 +40,18 @@ public class GBDaoGenerator {
private static final String TIMESTAMP_TO = "timestampTo";
public static void main(String[] args) throws Exception {
Schema schema = new Schema(14, MAIN_PACKAGE + ".entities");
Schema schema = new Schema(15, MAIN_PACKAGE + ".entities");
Entity userAttributes = addUserAttributes(schema);
Entity user = addUserInfo(schema, userAttributes);
Entity deviceAttributes = addDeviceAttributes(schema);
Entity device = addDevice(schema, deviceAttributes);
// yeah deep shit, has to be here (after device) for db upgrade and column order
// because addDevice adds a property to deviceAttributes also....
deviceAttributes.addStringProperty("volatileIdentifier");
Entity tag = addTag(schema);
Entity userDefinedActivityOverlay = addActivityDescription(schema, tag, user);

View File

@ -14,14 +14,22 @@ import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import de.greenrobot.dao.query.Query;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
import nodomain.freeyourgadget.gadgetbridge.activities.DiscoveryActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.GBActivity;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.entities.DeviceDao;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class PebblePairingActivity extends GBActivity {
@ -30,6 +38,7 @@ public class PebblePairingActivity extends GBActivity {
private boolean isPairing;
private boolean isLEPebble;
private String macAddress;
private BluetoothDevice mBtDevice;
private final BroadcastReceiver mPairingReceiver = new BroadcastReceiver() {
@Override
@ -59,7 +68,7 @@ public class PebblePairingActivity extends GBActivity {
if (bondState == BluetoothDevice.BOND_BONDED) {
LOG.info("Bonded with " + device.getAddress());
if (!isLEPebble) {
performConnect(device);
performConnect(null);
}
} else if (bondState == BluetoothDevice.BOND_BONDING) {
LOG.info("Bonding in progress with " + device.getAddress());
@ -84,12 +93,34 @@ public class PebblePairingActivity extends GBActivity {
macAddress = intent.getStringExtra(DeviceCoordinator.EXTRA_DEVICE_MAC_ADDRESS);
if (macAddress == null) {
Toast.makeText(this, getString(R.string.message_cannot_pair_no_mac), Toast.LENGTH_SHORT).show();
startActivity(new Intent(this, DiscoveryActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
finish();
returnToPairingActivity();
return;
}
startPairing();
mBtDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress);
if (mBtDevice == null) {
GB.toast(this, "No such Bluetooth Device: " + macAddress, Toast.LENGTH_LONG, GB.ERROR);
returnToPairingActivity();
return;
}
isLEPebble = mBtDevice.getType() == BluetoothDevice.DEVICE_TYPE_LE;
GBDevice gbDevice = null;
if (isLEPebble) {
if (mBtDevice.getName().startsWith("Pebble-LE ") || mBtDevice.getName().startsWith("Pebble Time LE ")) {
if (!GBApplication.getPrefs().getBoolean("pebble_force_le", false)) {
GB.toast(this, "Please switch on \"Always prefer BLE\" option in Pebble settings before pairing you Pebble LE", Toast.LENGTH_LONG, GB.ERROR);
returnToPairingActivity();
return;
}
gbDevice = getMatchingParentDeviceFromDB(mBtDevice);
if (gbDevice == null) {
return;
}
}
}
startPairing(gbDevice);
}
@Override
@ -104,10 +135,11 @@ public class PebblePairingActivity extends GBActivity {
if (isPairing) {
stopPairing();
}
super.onDestroy();
}
private void startPairing() {
private void startPairing(GBDevice gbDevice) {
isPairing = true;
message.setText(getString(R.string.pairing, macAddress));
@ -116,12 +148,7 @@ public class PebblePairingActivity extends GBActivity {
filter = new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED);
registerReceiver(mBondingReceiver, filter);
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress);
if (device != null) {
performPair(device);
} else {
GB.toast(this, "No such Bluetooth Device: " + macAddress, Toast.LENGTH_LONG, GB.ERROR);
}
performPair(gbDevice);
}
private void pairingFinished(boolean pairedSuccessfully) {
@ -147,31 +174,69 @@ public class PebblePairingActivity extends GBActivity {
isPairing = false;
}
protected void performPair(BluetoothDevice device) {
int bondState = device.getBondState();
protected void performPair(GBDevice gbDevice) {
int bondState = mBtDevice.getBondState();
if (bondState == BluetoothDevice.BOND_BONDED) {
GB.toast(getString(R.string.pairing_already_bonded, device.getName(), device.getAddress()), Toast.LENGTH_SHORT, GB.INFO);
GB.toast(getString(R.string.pairing_already_bonded, mBtDevice.getName(), mBtDevice.getAddress()), Toast.LENGTH_SHORT, GB.INFO);
return;
}
if (bondState == BluetoothDevice.BOND_BONDING) {
GB.toast(this, getString(R.string.pairing_in_progress, device.getName(), macAddress), Toast.LENGTH_LONG, GB.INFO);
GB.toast(this, getString(R.string.pairing_in_progress, mBtDevice.getName(), macAddress), Toast.LENGTH_LONG, GB.INFO);
return;
}
GB.toast(this, getString(R.string.pairing_creating_bond_with, device.getName(), macAddress), Toast.LENGTH_LONG, GB.INFO);
GB.toast(this, getString(R.string.pairing_creating_bond_with, mBtDevice.getName(), macAddress), Toast.LENGTH_LONG, GB.INFO);
GBApplication.deviceService().disconnect(); // just to make sure...
if (device.getType() == BluetoothDevice.DEVICE_TYPE_LE) {
isLEPebble = true;
performConnect(device);
if (isLEPebble) {
performConnect(gbDevice);
} else {
isLEPebble = false;
device.createBond();
mBtDevice.createBond();
}
}
private void performConnect(BluetoothDevice device) {
GBDevice gbDevice = new GBDevice(device.getAddress(), device.getName(), DeviceType.PEBBLE);
private void performConnect(GBDevice gbDevice) {
if (gbDevice == null) {
gbDevice = new GBDevice(mBtDevice.getAddress(), mBtDevice.getName(), DeviceType.PEBBLE);
}
GBApplication.deviceService().connect(gbDevice);
}
private void returnToPairingActivity() {
startActivity(new Intent(this, DiscoveryActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP));
finish();
}
private GBDevice getMatchingParentDeviceFromDB(BluetoothDevice btDevice) {
String expectedSuffix = btDevice.getName();
expectedSuffix = expectedSuffix.replace("Pebble-LE ", "");
expectedSuffix = expectedSuffix.replace("Pebble Time LE ", "");
expectedSuffix = expectedSuffix.substring(0, 2) + ":" + expectedSuffix.substring(2);
LOG.info("will try to find a Pebble with BT address suffix " + expectedSuffix);
GBDevice gbDevice = null;
try (DBHandler dbHandler = GBApplication.acquireDB()) {
DaoSession session = dbHandler.getDaoSession();
DeviceDao deviceDao = session.getDeviceDao();
Query<Device> query = deviceDao.queryBuilder().where(DeviceDao.Properties.Type.eq(1), DeviceDao.Properties.Identifier.like("%" + expectedSuffix)).build();
List<Device> devices = query.list();
if (devices.size() == 0) {
GB.toast("Please pair your non-LE Pebble before pairing the LE one", Toast.LENGTH_SHORT, GB.INFO);
returnToPairingActivity();
return null;
} else if (devices.size() > 1) {
GB.toast("Can not match this Pebble LE to a unique device", Toast.LENGTH_SHORT, GB.INFO);
returnToPairingActivity();
return null;
}
DeviceHelper deviceHelper = DeviceHelper.getInstance();
gbDevice = deviceHelper.toGBDevice(devices.get(0));
gbDevice.setVolatileAddress(btDevice.getAddress());
} catch (Exception e) {
GB.toast("Error retrieving devices from database", Toast.LENGTH_SHORT, GB.ERROR);
returnToPairingActivity();
return null;
}
return gbDevice;
}
}

View File

@ -45,8 +45,10 @@ public class GBDevice implements Parcelable {
private static final String DEVINFO_FW_VER = "FW: ";
private static final String DEVINFO_HR_VER = "HR: ";
private static final String DEVINFO_ADDR = "ADDR: ";
private static final String DEVINFO_ADDR2 = "ADDR2: ";
private String mName;
private final String mAddress;
private String mVolatileAddress;
private final DeviceType mDeviceType;
private String mFirmwareVersion;
private String mFirmwareVersion2;
@ -60,7 +62,12 @@ public class GBDevice implements Parcelable {
private List<ItemWithDetails> mDeviceInfos;
public GBDevice(String address, String name, DeviceType deviceType) {
this(address, null, name, deviceType);
}
public GBDevice(String address, String address2, String name, DeviceType deviceType) {
mAddress = address;
mVolatileAddress = address2;
mName = (name != null) ? name : mAddress;
mDeviceType = deviceType;
validate();
@ -69,6 +76,7 @@ public class GBDevice implements Parcelable {
private GBDevice(Parcel in) {
mName = in.readString();
mAddress = in.readString();
mVolatileAddress = in.readString();
mDeviceType = DeviceType.values()[in.readInt()];
mFirmwareVersion = in.readString();
mFirmwareVersion2 = in.readString();
@ -88,6 +96,7 @@ public class GBDevice implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mName);
dest.writeString(mAddress);
dest.writeString(mVolatileAddress);
dest.writeInt(mDeviceType.ordinal());
dest.writeString(mFirmwareVersion);
dest.writeString(mFirmwareVersion2);
@ -123,6 +132,10 @@ public class GBDevice implements Parcelable {
return mAddress;
}
public String getVolatileAddress() {
return mVolatileAddress;
}
public String getFirmwareVersion() {
return mFirmwareVersion;
}
@ -142,6 +155,10 @@ public class GBDevice implements Parcelable {
mFirmwareVersion2 = firmwareVersion2;
}
public void setVolatileAddress(String volatileAddress) {
mVolatileAddress = volatileAddress;
}
/**
* Returns the specific model/hardware revision of this device.
* This information is not always available, typically only when the device is initialized
@ -416,6 +433,9 @@ public class GBDevice implements Parcelable {
if (mAddress != null) {
result.add(new GenericItem(DEVINFO_ADDR, mAddress));
}
if (mVolatileAddress != null) {
result.add(new GenericItem(DEVINFO_ADDR2, mVolatileAddress));
}
Collections.sort(result);
return result;
}

View File

@ -174,8 +174,8 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
DaoSession session = dbHandler.getDaoSession();
if (DBHelper.findDevice(device, session) == null) {
DBHelper dbHelper = new DBHelper(context);
DBHelper.getDevice(device, session); // implicitly creates it :P
if (dbHelper.getOldActivityDatabaseHandler() != null) {
DBHelper.getDevice(device, session); // implicitly creates it :P
Intent startIntent = new Intent(context, OnboardingActivity.class);
startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, device);

View File

@ -64,7 +64,7 @@ class PebbleIoThread extends GBDeviceIoThread {
public static final String PEBBLEKIT_ACTION_APP_START = "com.getpebble.action.app.START";
public static final String PEBBLEKIT_ACTION_APP_STOP = "com.getpebble.action.app.STOP";
final Prefs prefs = GBApplication.getPrefs();
private final Prefs prefs = GBApplication.getPrefs();
private final PebbleProtocol mPebbleProtocol;
private final PebbleSupport mPebbleSupport;
@ -174,27 +174,31 @@ class PebbleIoThread extends GBDeviceIoThread {
}
@Override
protected boolean connect(String btDeviceAddress) {
protected boolean connect() {
String deviceAddress = gbDevice.getAddress();
GBDevice.State originalState = gbDevice.getState();
gbDevice.setState(GBDevice.State.CONNECTING);
gbDevice.sendDeviceUpdateIntent(getContext());
try {
// contains only one ":"? then it is addr:port
int firstColon = btDeviceAddress.indexOf(":");
if (firstColon == btDeviceAddress.lastIndexOf(":")) {
int firstColon = deviceAddress.indexOf(":");
if (firstColon == deviceAddress.lastIndexOf(":")) {
mIsTCP = true;
InetAddress serverAddr = InetAddress.getByName(btDeviceAddress.substring(0, firstColon));
mTCPSocket = new Socket(serverAddr, Integer.parseInt(btDeviceAddress.substring(firstColon + 1)));
InetAddress serverAddr = InetAddress.getByName(deviceAddress.substring(0, firstColon));
mTCPSocket = new Socket(serverAddr, Integer.parseInt(deviceAddress.substring(firstColon + 1)));
mInStream = mTCPSocket.getInputStream();
mOutStream = mTCPSocket.getOutputStream();
} else {
mIsTCP = false;
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(btDeviceAddress);
if (gbDevice.getVolatileAddress() != null && prefs.getBoolean("pebble_force_le", false)) {
deviceAddress = gbDevice.getVolatileAddress();
}
BluetoothDevice btDevice = mBtAdapter.getRemoteDevice(deviceAddress);
if (btDevice.getType() == BluetoothDevice.DEVICE_TYPE_LE) {
LOG.info("Ok this seems to be a LE Pebble, try LE Support, trouble ahead!");
mInStream = new PipedInputStream();
mOutStream = new PipedOutputStream();
mPebbleLESupport = new PebbleLESupport(this.getContext(),btDeviceAddress,(PipedInputStream)mInStream,(PipedOutputStream)mOutStream); // secret branch :P
mPebbleLESupport = new PebbleLESupport(this.getContext(), btDevice, (PipedInputStream) mInStream, (PipedOutputStream) mOutStream);
} else {
ParcelUuid uuids[] = btDevice.getUuids();
if (uuids == null) {
@ -232,7 +236,7 @@ class PebbleIoThread extends GBDeviceIoThread {
@Override
public void run() {
mIsConnected = connect(gbDevice.getAddress());
mIsConnected = connect();
if (!mIsConnected) {
if (GBApplication.getGBPrefs().getAutoReconnect()) {
gbDevice.setState(GBDevice.State.WAITING_FOR_RECONNECT);
@ -388,7 +392,7 @@ class PebbleIoThread extends GBDeviceIoThread {
int delaySeconds = 1;
while (reconnectAttempts-- > 0 && !mQuit && !mIsConnected) {
LOG.info("Trying to reconnect (attempts left " + reconnectAttempts + ")");
mIsConnected = connect(gbDevice.getAddress());
mIsConnected = connect();
if (!mIsConnected) {
try {
Thread.sleep(delaySeconds * 1000);

View File

@ -1,8 +1,6 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.pebble.ble;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import org.slf4j.Logger;
@ -22,8 +20,8 @@ public class PebbleLESupport {
private PipedOutputStream mPipedOutputStream;
private int mMTU = 20;
public PebbleLESupport(Context context, final String btDeviceAddress, PipedInputStream pipedInputStream, PipedOutputStream pipedOutputStream) {
public PebbleLESupport(Context context, final BluetoothDevice btDevice, PipedInputStream pipedInputStream, PipedOutputStream pipedOutputStream) {
mBtDevice = btDevice;
mPipedInputStream = new PipedInputStream();
mPipedOutputStream = new PipedOutputStream();
try {
@ -33,9 +31,6 @@ public class PebbleLESupport {
LOG.warn("could not connect input stream");
}
BluetoothManager manager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter adapter = manager.getAdapter();
mBtDevice = adapter.getRemoteDevice(btDeviceAddress);
mPebbleGATTServer = new PebbleGATTServer(this, context, mBtDevice);
mPebbleGATTServer.initialize();

View File

@ -21,7 +21,7 @@ public abstract class GBDeviceIoThread extends Thread {
return gbDevice;
}
protected boolean connect(String btDeviceAddress) {
protected boolean connect() {
return false;
}

View File

@ -192,7 +192,7 @@ public class DeviceHelper {
* @param dbDevice
* @return
*/
private GBDevice toGBDevice(Device dbDevice) {
public GBDevice toGBDevice(Device dbDevice) {
DeviceType deviceType = DeviceType.fromKey(dbDevice.getType());
GBDevice gbDevice = new GBDevice(dbDevice.getIdentifier(), dbDevice.getName(), deviceType);
List<DeviceAttributes> deviceAttributesList = dbDevice.getDeviceAttributesList();
@ -201,6 +201,7 @@ public class DeviceHelper {
DeviceAttributes attrs = deviceAttributesList.get(0);
gbDevice.setFirmwareVersion(attrs.getFirmwareVersion1());
gbDevice.setFirmwareVersion2(attrs.getFirmwareVersion2());
gbDevice.setVolatileAddress(attrs.getVolatileIdentifier());
}
return gbDevice;
@ -211,6 +212,9 @@ public class DeviceHelper {
List<GBDevice> result = new ArrayList<>(pairedDevices.size());
DeviceHelper deviceHelper = DeviceHelper.getInstance();
for (BluetoothDevice pairedDevice : pairedDevices) {
if (pairedDevice.getName() != null && (pairedDevice.getName().startsWith("Pebble-LE ") || pairedDevice.getName().startsWith("Pebble Time LE "))) {
continue; // ignore LE Pebble (this is part of the main device now (volatileAddress)
}
GBDevice device = deviceHelper.toSupportedDevice(pairedDevice);
if (device != null) {
result.add(device);

View File

@ -118,6 +118,8 @@
<string name="pref_summary_pebble_forceprotocol">This option forces using the latest notification protocol depending on the firmware version. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING!</string>
<string name="pref_title_pebble_forceuntested">Enable untested features</string>
<string name="pref_summary_pebble_forceuntested">Enable features that are untested. ENABLE ONLY IF YOU KNOW WHAT YOU ARE DOING!</string>
<string name="pref_title_pebble_forcele">Always prefer BLE</string>
<string name="pref_summary_pebble_forcele">Use experimental Pebble LE support for all Pebbles instead of BT classic, requires paring a "Pebble LE" after non LE had been connected once</string>
<string name="pref_title_pebble_reconnect_attempts">Reconnection Attempts</string>
<string name="not_connected">not connected</string>

View File

@ -336,6 +336,11 @@
android:key="pebble_force_untested"
android:summary="@string/pref_summary_pebble_forceuntested"
android:title="@string/pref_title_pebble_forceuntested" />
<CheckBoxPreference
android:defaultValue="false"
android:key="pebble_force_le"
android:summary="@string/pref_summary_pebble_forcele"
android:title="@string/pref_title_pebble_forcele" />
<EditTextPreference
android:digits="0123456789."
android:key="pebble_emu_addr"