Some babysteps towards miband2 support #323

Start to implement standard BLE profiles/services.
master
cpfeiffer 2016-07-25 00:00:22 +02:00
parent df59ce7b96
commit f5ba09ebe0
13 changed files with 1925 additions and 9 deletions

View File

@ -13,8 +13,9 @@ public class MiBandService {
public static final String MAC_ADDRESS_FILTER_1S = "C8:0F:10";
public static final UUID UUID_SERVICE_MIBAND_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE0"));
public static final UUID UUID_SERVICE_MIBAND2_SERVICE = UUID.fromString(String.format(BASE_UUID, "FEE1"));
public static final UUID UUID_SERVICE_HEART_RATE = UUID.fromString(String.format(BASE_UUID, "180D"));
public static final String UUID_SERVICE_WEIGHT_SERVICE = "00001530-0000-3512-2118-0009af100700";
public static final UUID UUID_CHARACTERISTIC_DEVICE_INFO = UUID.fromString(String.format(BASE_UUID, "FF01"));
@ -53,8 +54,6 @@ public class MiBandService {
/* FURTHER UUIDS that were mixed with the other params below. The base UUID for these is unknown */
public static final String UUID_SERVICE_WEIGHT_SERVICE = "00001530-0000-3512-2118-0009af100700";
public static final byte ALIAS_LEN = 0xa;
/*NOTIFICATIONS: usually received on the UUID_CHARACTERISTIC_NOTIFICATION characteristic */

View File

@ -9,6 +9,8 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -17,6 +19,7 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
/**
* Abstract base class for all devices connected through Bluetooth Low Energy (LE) aka
@ -35,6 +38,7 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
private BtLEQueue mQueue;
private HashMap<UUID, BluetoothGattCharacteristic> mAvailableCharacteristics;
private final Set<UUID> mSupportedServices = new HashSet<>(4);
private final List<AbstractBleProfile<?>> mSupportedProfiles = new ArrayList<>();
public static final String BASE_UUID = "0000%s-0000-1000-8000-00805f9b34fb"; //this is common for all BTLE devices. see http://stackoverflow.com/questions/18699251/finding-out-android-bluetooth-le-gatt-profiles
@ -131,6 +135,10 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
mSupportedServices.add(aSupportedService);
}
protected void addSupportedProfile(AbstractBleProfile<?> profile) {
mSupportedProfiles.add(profile);
}
/**
* Returns the characteristic matching the given UUID. Only characteristics
* are returned whose service is marked as supported.
@ -155,7 +163,7 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
mAvailableCharacteristics = new HashMap<>();
for (BluetoothGattService service : discoveredGattServices) {
if (supportedServices.contains(service.getUuid())) {
LOG.debug("discovered supported service: " + service.getUuid());
LOG.debug("discovered supported service: " + BleNamesResolver.resolveServiceName(service.getUuid().toString()) + ": " + service.getUuid());
List<BluetoothGattCharacteristic> characteristics = service.getCharacteristics();
if (characteristics == null || characteristics.isEmpty()) {
LOG.warn("Supported LE service " + service.getUuid() + "did not return any characteristics");
@ -164,10 +172,11 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
HashMap<UUID, BluetoothGattCharacteristic> intmAvailableCharacteristics = new HashMap<>(characteristics.size());
for (BluetoothGattCharacteristic characteristic : characteristics) {
intmAvailableCharacteristics.put(characteristic.getUuid(), characteristic);
LOG.info(" characteristic: " + BleNamesResolver.resolveCharacteristicName(characteristic.getUuid().toString()) + ": " + characteristic.getUuid());
}
mAvailableCharacteristics.putAll(intmAvailableCharacteristics);
} else {
LOG.debug("discovered unsupported service: " + service.getUuid());
LOG.debug("discovered unsupported service: " + BleNamesResolver.resolveServiceName(service.getUuid().toString()) + ": " + service.getUuid());
}
}
}
@ -179,6 +188,9 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
// default implementations of event handler methods (gatt callbacks)
@Override
public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
for (AbstractBleProfile profile : mSupportedProfiles) {
profile.onConnectionStateChange(gatt, status, newState);
}
}
@Override
@ -190,27 +202,45 @@ public abstract class AbstractBTLEDeviceSupport extends AbstractDeviceSupport im
@Override
public void onCharacteristicRead(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
for (AbstractBleProfile profile : mSupportedProfiles) {
profile.onCharacteristicRead(gatt, characteristic, status);
}
}
@Override
public void onCharacteristicWrite(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic, int status) {
for (AbstractBleProfile profile : mSupportedProfiles) {
profile.onCharacteristicWrite(gatt, characteristic, status);
}
}
@Override
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
for (AbstractBleProfile profile : mSupportedProfiles) {
profile.onDescriptorRead(gatt, descriptor, status);
}
}
@Override
public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
for (AbstractBleProfile profile : mSupportedProfiles) {
profile.onDescriptorWrite(gatt, descriptor, status);
}
}
@Override
public void onCharacteristicChanged(BluetoothGatt gatt,
BluetoothGattCharacteristic characteristic) {
for (AbstractBleProfile profile : mSupportedProfiles) {
profile.onCharacteristicChanged(gatt, characteristic);
}
}
@Override
public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
for (AbstractBleProfile profile : mSupportedProfiles) {
profile.onReadRemoteRssi(gatt, rssi, status);
}
}
}

View File

@ -0,0 +1,203 @@
package nodomain.freeyourgadget.gadgetbridge.service.btle;
import java.util.HashMap;
import android.util.SparseArray;
public class BleNamesResolver {
private static HashMap<String, String> mServices = new HashMap<String, String>();
private static HashMap<String, String> mCharacteristics = new HashMap<String, String>();
private static SparseArray<String> mValueFormats = new SparseArray<String>();
private static SparseArray<String> mAppearance = new SparseArray<String>();
private static SparseArray<String> mHeartRateSensorLocation = new SparseArray<String>();
static public String resolveServiceName(final String uuid)
{
String result = mServices.get(uuid);
if(result == null) result = "Unknown Service";
return result;
}
static public String resolveValueTypeDescription(final int format)
{
Integer tmp = Integer.valueOf(format);
return mValueFormats.get(tmp, "Unknown Format");
}
static public String resolveCharacteristicName(final String uuid)
{
String result = mCharacteristics.get(uuid);
if(result == null) result = "Unknown Characteristic";
return result;
}
static public String resolveUuid(final String uuid) {
String result = mServices.get(uuid);
if(result != null) return "Service: " + result;
result = mCharacteristics.get(uuid);
if(result != null) return "Characteristic: " + result;
result = "Unknown UUID";
return result;
}
static public String resolveAppearance(int key) {
Integer tmp = Integer.valueOf(key);
return mAppearance.get(tmp, "Unknown Appearance");
}
static public String resolveHeartRateSensorLocation(int key) {
Integer tmp = Integer.valueOf(key);
return mHeartRateSensorLocation.get(tmp, "Other");
}
static public boolean isService(final String uuid) {
return mServices.containsKey(uuid);
}
static public boolean isCharacteristic(final String uuid) {
return mCharacteristics.containsKey(uuid);
}
static {
mServices.put("00001811-0000-1000-8000-00805f9b34fb", "Alert Notification Service");
mServices.put("0000180f-0000-1000-8000-00805f9b34fb", "Battery Service");
mServices.put("00001810-0000-1000-8000-00805f9b34fb", "Blood Pressure");
mServices.put("00001805-0000-1000-8000-00805f9b34fb", "Current Time Service");
mServices.put("00001818-0000-1000-8000-00805f9b34fb", "Cycling Power");
mServices.put("00001816-0000-1000-8000-00805f9b34fb", "Cycling Speed and Cadence");
mServices.put("0000180a-0000-1000-8000-00805f9b34fb", "Device Information");
mServices.put("00001800-0000-1000-8000-00805f9b34fb", "Generic Access");
mServices.put("00001801-0000-1000-8000-00805f9b34fb", "Generic Attribute");
mServices.put("00001808-0000-1000-8000-00805f9b34fb", "Glucose");
mServices.put("00001809-0000-1000-8000-00805f9b34fb", "Health Thermometer");
mServices.put("0000180d-0000-1000-8000-00805f9b34fb", "Heart Rate");
mServices.put("00001812-0000-1000-8000-00805f9b34fb", "Human Interface Device");
mServices.put("00001802-0000-1000-8000-00805f9b34fb", "Immediate Alert");
mServices.put("00001803-0000-1000-8000-00805f9b34fb", "Link Loss");
mServices.put("00001819-0000-1000-8000-00805f9b34fb", "Location and Navigation");
mServices.put("00001807-0000-1000-8000-00805f9b34fb", "Next DST Change Service");
mServices.put("0000180e-0000-1000-8000-00805f9b34fb", "Phone Alert Status Service");
mServices.put("00001806-0000-1000-8000-00805f9b34fb", "Reference Time Update Service");
mServices.put("00001814-0000-1000-8000-00805f9b34fb", "Running Speed and Cadence");
mServices.put("00001813-0000-1000-8000-00805f9b34fb", "Scan Parameters");
mServices.put("00001804-0000-1000-8000-00805f9b34fb", "Tx Power");
mServices.put("0000fee0-0000-3512-2118-0009af100700", "(Propr: Xiaomi MiLi Service)");
mServices.put("00001530-0000-3512-2118-0009af100700", "(Propr: Xiaomi Weight Service)");
mCharacteristics.put("00002a43-0000-1000-8000-00805f9b34fb", "Alert Category ID");
mCharacteristics.put("00002a42-0000-1000-8000-00805f9b34fb", "Alert Category ID Bit Mask");
mCharacteristics.put("00002a06-0000-1000-8000-00805f9b34fb", "Alert Level");
mCharacteristics.put("00002a44-0000-1000-8000-00805f9b34fb", "Alert Notification Control Point");
mCharacteristics.put("00002a3f-0000-1000-8000-00805f9b34fb", "Alert Status");
mCharacteristics.put("00002a01-0000-1000-8000-00805f9b34fb", "Appearance");
mCharacteristics.put("00002a19-0000-1000-8000-00805f9b34fb", "Battery Level");
mCharacteristics.put("00002a49-0000-1000-8000-00805f9b34fb", "Blood Pressure Feature");
mCharacteristics.put("00002a35-0000-1000-8000-00805f9b34fb", "Blood Pressure Measurement");
mCharacteristics.put("00002a38-0000-1000-8000-00805f9b34fb", "Body Sensor Location");
mCharacteristics.put("00002a22-0000-1000-8000-00805f9b34fb", "Boot Keyboard Input Report");
mCharacteristics.put("00002a32-0000-1000-8000-00805f9b34fb", "Boot Keyboard Output Report");
mCharacteristics.put("00002a33-0000-1000-8000-00805f9b34fb", "Boot Mouse Input Report");
mCharacteristics.put("00002a5c-0000-1000-8000-00805f9b34fb", "CSC Feature");
mCharacteristics.put("00002a5b-0000-1000-8000-00805f9b34fb", "CSC Measurement");
mCharacteristics.put("00002a2b-0000-1000-8000-00805f9b34fb", "Current Time");
mCharacteristics.put("00002a66-0000-1000-8000-00805f9b34fb", "Cycling Power Control Point");
mCharacteristics.put("00002a65-0000-1000-8000-00805f9b34fb", "Cycling Power Feature");
mCharacteristics.put("00002a63-0000-1000-8000-00805f9b34fb", "Cycling Power Measurement");
mCharacteristics.put("00002a64-0000-1000-8000-00805f9b34fb", "Cycling Power Vector");
mCharacteristics.put("00002a08-0000-1000-8000-00805f9b34fb", "Date Time");
mCharacteristics.put("00002a0a-0000-1000-8000-00805f9b34fb", "Day Date Time");
mCharacteristics.put("00002a09-0000-1000-8000-00805f9b34fb", "Day of Week");
mCharacteristics.put("00002a00-0000-1000-8000-00805f9b34fb", "Device Name");
mCharacteristics.put("00002a0d-0000-1000-8000-00805f9b34fb", "DST Offset");
mCharacteristics.put("00002a0c-0000-1000-8000-00805f9b34fb", "Exact Time 256");
mCharacteristics.put("00002a26-0000-1000-8000-00805f9b34fb", "Firmware Revision String");
mCharacteristics.put("00002a51-0000-1000-8000-00805f9b34fb", "Glucose Feature");
mCharacteristics.put("00002a18-0000-1000-8000-00805f9b34fb", "Glucose Measurement");
mCharacteristics.put("00002a34-0000-1000-8000-00805f9b34fb", "Glucose Measurement Context");
mCharacteristics.put("00002a27-0000-1000-8000-00805f9b34fb", "Hardware Revision String");
mCharacteristics.put("00002a39-0000-1000-8000-00805f9b34fb", "Heart Rate Control Point");
mCharacteristics.put("00002a37-0000-1000-8000-00805f9b34fb", "Heart Rate Measurement");
mCharacteristics.put("00002a4c-0000-1000-8000-00805f9b34fb", "HID Control Point");
mCharacteristics.put("00002a4a-0000-1000-8000-00805f9b34fb", "HID Information");
mCharacteristics.put("00002a2a-0000-1000-8000-00805f9b34fb", "IEEE 11073-20601 Regulatory Certification Data List");
mCharacteristics.put("00002a36-0000-1000-8000-00805f9b34fb", "Intermediate Cuff Pressure");
mCharacteristics.put("00002a1e-0000-1000-8000-00805f9b34fb", "Intermediate Temperature");
mCharacteristics.put("00002a6b-0000-1000-8000-00805f9b34fb", "LN Control Point");
mCharacteristics.put("00002a6a-0000-1000-8000-00805f9b34fb", "LN Feature");
mCharacteristics.put("00002a0f-0000-1000-8000-00805f9b34fb", "Local Time Information");
mCharacteristics.put("00002a67-0000-1000-8000-00805f9b34fb", "Location and Speed");
mCharacteristics.put("00002a29-0000-1000-8000-00805f9b34fb", "Manufacturer Name String");
mCharacteristics.put("00002a21-0000-1000-8000-00805f9b34fb", "Measurement Interval");
mCharacteristics.put("00002a24-0000-1000-8000-00805f9b34fb", "Model Number String");
mCharacteristics.put("00002a68-0000-1000-8000-00805f9b34fb", "Navigation");
mCharacteristics.put("00002a46-0000-1000-8000-00805f9b34fb", "New Alert");
mCharacteristics.put("00002a04-0000-1000-8000-00805f9b34fb", "Peripheral Preferred Connection Parameters");
mCharacteristics.put("00002a02-0000-1000-8000-00805f9b34fb", "Peripheral Privacy Flag");
mCharacteristics.put("00002a50-0000-1000-8000-00805f9b34fb", "PnP ID");
mCharacteristics.put("00002a69-0000-1000-8000-00805f9b34fb", "Position Quality");
mCharacteristics.put("00002a4e-0000-1000-8000-00805f9b34fb", "Protocol Mode");
mCharacteristics.put("00002a03-0000-1000-8000-00805f9b34fb", "Reconnection Address");
mCharacteristics.put("00002a52-0000-1000-8000-00805f9b34fb", "Record Access Control Point");
mCharacteristics.put("00002a14-0000-1000-8000-00805f9b34fb", "Reference Time Information");
mCharacteristics.put("00002a4d-0000-1000-8000-00805f9b34fb", "Report");
mCharacteristics.put("00002a4b-0000-1000-8000-00805f9b34fb", "Report Map");
mCharacteristics.put("00002a40-0000-1000-8000-00805f9b34fb", "Ringer Control Point");
mCharacteristics.put("00002a41-0000-1000-8000-00805f9b34fb", "Ringer Setting");
mCharacteristics.put("00002a54-0000-1000-8000-00805f9b34fb", "RSC Feature");
mCharacteristics.put("00002a53-0000-1000-8000-00805f9b34fb", "RSC Measurement");
mCharacteristics.put("00002a55-0000-1000-8000-00805f9b34fb", "SC Control Point");
mCharacteristics.put("00002a4f-0000-1000-8000-00805f9b34fb", "Scan Interval Window");
mCharacteristics.put("00002a31-0000-1000-8000-00805f9b34fb", "Scan Refresh");
mCharacteristics.put("00002a5d-0000-1000-8000-00805f9b34fb", "Sensor Location");
mCharacteristics.put("00002a25-0000-1000-8000-00805f9b34fb", "Serial Number String");
mCharacteristics.put("00002a05-0000-1000-8000-00805f9b34fb", "Service Changed");
mCharacteristics.put("00002a28-0000-1000-8000-00805f9b34fb", "Software Revision String");
mCharacteristics.put("00002a47-0000-1000-8000-00805f9b34fb", "Supported New Alert Category");
mCharacteristics.put("00002a48-0000-1000-8000-00805f9b34fb", "Supported Unread Alert Category");
mCharacteristics.put("00002a23-0000-1000-8000-00805f9b34fb", "System ID");
mCharacteristics.put("00002a1c-0000-1000-8000-00805f9b34fb", "Temperature Measurement");
mCharacteristics.put("00002a1d-0000-1000-8000-00805f9b34fb", "Temperature Type");
mCharacteristics.put("00002a12-0000-1000-8000-00805f9b34fb", "Time Accuracy");
mCharacteristics.put("00002a13-0000-1000-8000-00805f9b34fb", "Time Source");
mCharacteristics.put("00002a16-0000-1000-8000-00805f9b34fb", "Time Update Control Point");
mCharacteristics.put("00002a17-0000-1000-8000-00805f9b34fb", "Time Update State");
mCharacteristics.put("00002a11-0000-1000-8000-00805f9b34fb", "Time with DST");
mCharacteristics.put("00002a0e-0000-1000-8000-00805f9b34fb", "Time Zone");
mCharacteristics.put("00002a07-0000-1000-8000-00805f9b34fb", "Tx Power Level");
mCharacteristics.put("00002a45-0000-1000-8000-00805f9b34fb", "Unread Alert Status");
mValueFormats.put(Integer.valueOf(52), "32bit float");
mValueFormats.put(Integer.valueOf(50), "16bit float");
mValueFormats.put(Integer.valueOf(34), "16bit signed int");
mValueFormats.put(Integer.valueOf(36), "32bit signed int");
mValueFormats.put(Integer.valueOf(33), "8bit signed int");
mValueFormats.put(Integer.valueOf(18), "16bit unsigned int");
mValueFormats.put(Integer.valueOf(20), "32bit unsigned int");
mValueFormats.put(Integer.valueOf(17), "8bit unsigned int");
// lets add also couple appearance string description
// https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.gap.appearance.xml
mAppearance.put(Integer.valueOf(833), "Heart Rate Sensor: Belt");
mAppearance.put(Integer.valueOf(832), "Generic Heart Rate Sensor");
mAppearance.put(Integer.valueOf(0), "Unknown");
mAppearance.put(Integer.valueOf(64), "Generic Phone");
mAppearance.put(Integer.valueOf(1157), "Cycling: Speed and Cadence Sensor");
mAppearance.put(Integer.valueOf(1152), "General Cycling");
mAppearance.put(Integer.valueOf(1153), "Cycling Computer");
mAppearance.put(Integer.valueOf(1154), "Cycling: Speed Sensor");
mAppearance.put(Integer.valueOf(1155), "Cycling: Cadence Sensor");
mAppearance.put(Integer.valueOf(1156), "Cycling: Speed and Cadence Sensor");
mAppearance.put(Integer.valueOf(1157), "Cycling: Power Sensor");
mHeartRateSensorLocation.put(Integer.valueOf(0), "Other");
mHeartRateSensorLocation.put(Integer.valueOf(1), "Chest");
mHeartRateSensorLocation.put(Integer.valueOf(2), "Wrist");
mHeartRateSensorLocation.put(Integer.valueOf(3), "Finger");
mHeartRateSensorLocation.put(Integer.valueOf(4), "Hand");
mHeartRateSensorLocation.put(Integer.valueOf(5), "Ear Lobe");
mHeartRateSensorLocation.put(Integer.valueOf(6), "Foot");
}
}

View File

@ -1,5 +1,7 @@
package nodomain.freeyourgadget.gadgetbridge.service.btle;
import android.bluetooth.BluetoothGattCharacteristic;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@ -241,4 +243,7 @@ public class GattCharacteristic {
return name;
}
public static String toString(BluetoothGattCharacteristic characteristic) {
return characteristic.getUuid() + " (" + lookup(characteristic.getUuid(), "unknown") + ")";
}
}

View File

@ -0,0 +1,73 @@
package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import java.io.IOException;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractGattCallback;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEQueue;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
/**
* Base class for all BLE profiles, with things that all impplementations are
* expected to use.
*
* Instances are used in the context of a concrete AbstractBTLEDeviceSupport instance,
* i.e. a concrete device.
*
* @see nodomain.freeyourgadget.gadgetbridge.service.btle.GattService
* @see nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic
* @see https://www.bluetooth.com/specifications/assigned-numbers
*/
public abstract class AbstractBleProfile<T extends AbstractBTLEDeviceSupport> extends AbstractGattCallback {
private final T mSupport;
public AbstractBleProfile(T support) {
this.mSupport = support;
}
/**
* All notifications should be sent through this methods to make them testable.
* @param intent the intent to broadcast
*/
protected void notify(Intent intent) {
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent);
}
/**
* Delegates to the DeviceSupport instance and additionally sets this instance as the Gatt
* callback for the transaction.
*
* @param taskName
* @return
* @throws IOException
*/
public TransactionBuilder performInitialized(String taskName) throws IOException {
TransactionBuilder builder = mSupport.performInitialized(taskName);
builder.setGattCallback(this);
return builder;
}
public Context getContext() {
return mSupport.getContext();
}
protected GBDevice getDevice() {
return mSupport.getDevice();
}
protected BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
return mSupport.getCharacteristic(uuid);
}
protected BtLEQueue getQueue() {
return mSupport.getQueue();
}
}

View File

@ -0,0 +1,21 @@
package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles;
import android.bluetooth.BluetoothGattCharacteristic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
public class ValueDecoder {
private static final Logger LOG = LoggerFactory.getLogger(ValueDecoder.class);
public static int decodePercent(BluetoothGattCharacteristic characteristic) {
int percent = characteristic.getIntValue(BluetoothGattCharacteristic.FORMAT_UINT8, 0);
if (percent > 100 || percent < 0) {
LOG.warn("Unexpected percent value: " + percent + ": " + GattCharacteristic.toString(characteristic));
percent = Math.max(100, Math.min(0, percent));
}
return percent;
}
}

View File

@ -0,0 +1,46 @@
package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery;
import android.os.Parcel;
import android.os.Parcelable;
public class BatteryInfo implements Parcelable{
private int percentCharged;
public BatteryInfo() {
}
protected BatteryInfo(Parcel in) {
percentCharged = in.readInt();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(percentCharged);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<BatteryInfo> CREATOR = new Creator<BatteryInfo>() {
@Override
public BatteryInfo createFromParcel(Parcel in) {
return new BatteryInfo(in);
}
@Override
public BatteryInfo[] newArray(int size) {
return new BatteryInfo[size];
}
};
public int getPercentCharged() {
return percentCharged;
}
public void setPercentCharged(int percentCharged) {
this.percentCharged = percentCharged;
}
}

View File

@ -0,0 +1,71 @@
package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Intent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.ValueDecoder;
public class BatteryInfoProfile<T extends AbstractBTLEDeviceSupport> extends AbstractBleProfile {
private static final Logger LOG = LoggerFactory.getLogger(BatteryInfoProfile.class);
private static final String ACTION_PREFIX = BatteryInfoProfile.class.getName() + "_";
public static final String ACTION_BATTERY_INFO = ACTION_PREFIX + "BATTERY_INFO";
public static final String EXTRA_BATTERY_INFO = "BATTERY_INFO";
public static final UUID SERVICE_UUID = GattService.UUID_SERVICE_BATTERY_SERVICE;
public static final UUID UUID_CHARACTERISTIC_BATTERY_LEVEL = GattCharacteristic.UUID_CHARACTERISTIC_BATTERY_LEVEL;
private BatteryInfo batteryInfo;
public BatteryInfoProfile(T support) {
super(support);
}
public void requestBatteryInfo(TransactionBuilder builder) {
builder.read(getCharacteristic(UUID_CHARACTERISTIC_BATTERY_LEVEL));
}
public void enableNotifiy() {
// TODO: notification
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
UUID charUuid = characteristic.getUuid();
if (charUuid.equals(UUID_CHARACTERISTIC_BATTERY_LEVEL)) {
handleBatteryLevel(gatt, characteristic);
} else {
LOG.info("Unexpected onCharacteristicRead: " + GattCharacteristic.toString(characteristic));
}
} else {
LOG.warn("error reading from characteristic:" + GattCharacteristic.toString(characteristic));
}
}
private void handleBatteryLevel(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
int percent = ValueDecoder.decodePercent(characteristic);
batteryInfo.setPercentCharged(percent);
notify(createIntent(batteryInfo));
}
private Intent createIntent(BatteryInfo batteryInfo) {
Intent intent = new Intent(ACTION_BATTERY_INFO);
intent.putExtra(EXTRA_BATTERY_INFO, batteryInfo);
return intent;
}
}

View File

@ -0,0 +1,133 @@
package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo;
import android.os.Parcel;
import android.os.Parcelable;
public class DeviceInfo implements Parcelable{
private String manufacturerName;
private String modelNumber;
private String serialNumber;
private String hardwareRevision;
private String firmwareRevision;
private String softwareRevision;
private String systemId;
private String regulatoryCertificationDataList;
private String pnpId;
public DeviceInfo() {
}
protected DeviceInfo(Parcel in) {
manufacturerName = in.readString();
modelNumber = in.readString();
serialNumber = in.readString();
hardwareRevision = in.readString();
firmwareRevision = in.readString();
softwareRevision = in.readString();
systemId = in.readString();
regulatoryCertificationDataList = in.readString();
pnpId = in.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(manufacturerName);
dest.writeString(modelNumber);
dest.writeString(serialNumber);
dest.writeString(hardwareRevision);
dest.writeString(firmwareRevision);
dest.writeString(softwareRevision);
dest.writeString(systemId);
dest.writeString(regulatoryCertificationDataList);
dest.writeString(pnpId);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<DeviceInfo> CREATOR = new Creator<DeviceInfo>() {
@Override
public DeviceInfo createFromParcel(Parcel in) {
return new DeviceInfo(in);
}
@Override
public DeviceInfo[] newArray(int size) {
return new DeviceInfo[size];
}
};
public String getManufacturerName() {
return manufacturerName;
}
public void setManufacturerName(String manufacturerName) {
this.manufacturerName = manufacturerName;
}
public String getModelNumber() {
return modelNumber;
}
public void setModelNumber(String modelNumber) {
this.modelNumber = modelNumber;
}
public String getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(String serialNumber) {
this.serialNumber = serialNumber;
}
public String getHardwareRevision() {
return hardwareRevision;
}
public void setHardwareRevision(String hardwareRevision) {
this.hardwareRevision = hardwareRevision;
}
public String getFirmwareRevision() {
return firmwareRevision;
}
public void setFirmwareRevision(String firmwareRevision) {
this.firmwareRevision = firmwareRevision;
}
public String getSoftwareRevision() {
return softwareRevision;
}
public void setSoftwareRevision(String softwareRevision) {
this.softwareRevision = softwareRevision;
}
public String getSystemId() {
return systemId;
}
public void setSystemId(String systemId) {
this.systemId = systemId;
}
public String getRegulatoryCertificationDataList() {
return regulatoryCertificationDataList;
}
public void setRegulatoryCertificationDataList(String regulatoryCertificationDataList) {
this.regulatoryCertificationDataList = regulatoryCertificationDataList;
}
public String getPnpId() {
return pnpId;
}
public void setPnpId(String pnpId) {
this.pnpId = pnpId;
}
}

View File

@ -0,0 +1,148 @@
package nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.deviceinfo;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Intent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.AbstractBleProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.profiles.battery.BatteryInfo;
public class DeviceInfoProfile<T extends AbstractBTLEDeviceSupport> extends AbstractBleProfile {
private static final Logger LOG = LoggerFactory.getLogger(DeviceInfoProfile.class);
private static final String ACTION_PREFIX = DeviceInfoProfile.class.getName() + "_";
public static final String ACTION_DEVICE_INFO = ACTION_PREFIX + "DEVICE_INFO";
public static final String EXTRA_DEVICE_INFO = "DEVICE_INFO";
public static final UUID SERVICE_UUID = GattService.UUID_SERVICE_DEVICE_INFORMATION;
public static final UUID UUID_CHARACTERISTIC_MANUFACTURER_NAME_STRING = GattCharacteristic.UUID_CHARACTERISTIC_MANUFACTURER_NAME_STRING;
public static final UUID UUID_CHARACTERISTIC_MODEL_NUMBER_STRING = GattCharacteristic.UUID_CHARACTERISTIC_MODEL_NUMBER_STRING;
public static final UUID UUID_CHARACTERISTIC_SERIAL_NUMBER_STRING = GattCharacteristic.UUID_CHARACTERISTIC_SERIAL_NUMBER_STRING;
public static final UUID UUID_CHARACTERISTIC_HARDWARE_REVISION_STRING = GattCharacteristic.UUID_CHARACTERISTIC_HARDWARE_REVISION_STRING;
public static final UUID UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING = GattCharacteristic.UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING;
public static final UUID UUID_CHARACTERISTIC_SOFTWARE_REVISION_STRING = GattCharacteristic.UUID_CHARACTERISTIC_SOFTWARE_REVISION_STRING;
public static final UUID UUID_CHARACTERISTIC_SYSTEM_ID = GattCharacteristic.UUID_CHARACTERISTIC_SYSTEM_ID;
public static final UUID UUID_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST = GattCharacteristic.UUID_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST;
public static final UUID UUID_CHARACTERISTIC_PNP_ID = GattCharacteristic.UUID_CHARACTERISTIC_PNP_ID;
private final DeviceInfo deviceInfo = new DeviceInfo();
public DeviceInfoProfile(T support) {
super(support);
}
public void requestDeviceInfo(TransactionBuilder builder) {
builder.read(getCharacteristic(UUID_CHARACTERISTIC_MANUFACTURER_NAME_STRING))
.read(getCharacteristic(UUID_CHARACTERISTIC_MODEL_NUMBER_STRING))
.read(getCharacteristic(UUID_CHARACTERISTIC_SERIAL_NUMBER_STRING))
.read(getCharacteristic(UUID_CHARACTERISTIC_HARDWARE_REVISION_STRING))
.read(getCharacteristic(UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING))
.read(getCharacteristic(UUID_CHARACTERISTIC_SOFTWARE_REVISION_STRING))
.read(getCharacteristic(UUID_CHARACTERISTIC_SYSTEM_ID))
.read(getCharacteristic(UUID_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST))
.read(getCharacteristic(UUID_CHARACTERISTIC_PNP_ID));
}
@Override
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
if (status == BluetoothGatt.GATT_SUCCESS) {
UUID charUuid = characteristic.getUuid();
if (charUuid.equals(UUID_CHARACTERISTIC_MANUFACTURER_NAME_STRING)) {
handleManufacturerName(gatt, characteristic);
} else if (charUuid.equals(UUID_CHARACTERISTIC_MODEL_NUMBER_STRING)) {
handleModelNumber(gatt, characteristic);
} else if (charUuid.equals(UUID_CHARACTERISTIC_SERIAL_NUMBER_STRING)) {
handleSerialNumber(gatt, characteristic);
} else if (charUuid.equals(UUID_CHARACTERISTIC_HARDWARE_REVISION_STRING)) {
handleHardwareRevision(gatt, characteristic);
} else if (charUuid.equals(UUID_CHARACTERISTIC_FIRMWARE_REVISION_STRING)) {
handleFirmwareRevision(gatt, characteristic);
} else if (charUuid.equals(UUID_CHARACTERISTIC_SOFTWARE_REVISION_STRING)) {
handleSoftwareRevision(gatt, characteristic);
} else if (charUuid.equals(UUID_CHARACTERISTIC_SYSTEM_ID)) {
handleSystemId(gatt, characteristic);
} else if (charUuid.equals(UUID_CHARACTERISTIC_IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST)) {
handleRegulatoryCertificationData(gatt, characteristic);
} else if (charUuid.equals(UUID_CHARACTERISTIC_PNP_ID)) {
handlePnpId(gatt, characteristic);
} else {
LOG.info("Unexpected onCharacteristicRead: " + GattCharacteristic.toString(characteristic));
}
} else {
LOG.warn("error reading from characteristic:" + GattCharacteristic.toString(characteristic));
}
}
private void handleManufacturerName(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
String name = characteristic.getStringValue(0);
deviceInfo.setManufacturerName(name);
notify(createIntent(deviceInfo));
}
private void handleModelNumber(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
String modelNumber = characteristic.getStringValue(0);
deviceInfo.setModelNumber(modelNumber);
notify(createIntent(deviceInfo));
}
private void handleSerialNumber(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
String serialNumber = characteristic.getStringValue(0);
deviceInfo.setSerialNumber(serialNumber);
notify(createIntent(deviceInfo));
}
private void handleHardwareRevision(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
String hardwareRevision = characteristic.getStringValue(0);
deviceInfo.setHardwareRevision(hardwareRevision);
notify(createIntent(deviceInfo));
}
private void handleFirmwareRevision(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
String firmwareRevision = characteristic.getStringValue(0);
deviceInfo.setFirmwareRevision(firmwareRevision);
notify(createIntent(deviceInfo));
}
private void handleSoftwareRevision(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
String softwareRevision = characteristic.getStringValue(0);
deviceInfo.setSoftwareRevision(softwareRevision);
notify(createIntent(deviceInfo));
}
private void handleSystemId(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
String systemId = characteristic.getStringValue(0);
deviceInfo.setSystemId(systemId);
notify(createIntent(deviceInfo));
}
private void handleRegulatoryCertificationData(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
// TODO: regulatory certification data list not supported yet
// String regulatoryCertificationData = characteristic.getStringValue(0);
// deviceInfo.setRegulatoryCertificationDataList(regulatoryCertificationData);
// notify(createIntent(deviceInfo));
}
private void handlePnpId(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
String pnpId = characteristic.getStringValue(0);
deviceInfo.setPnpId(pnpId);
notify(createIntent(deviceInfo));
}
private Intent createIntent(DeviceInfo deviceInfo) {
Intent intent = new Intent(ACTION_DEVICE_INFO);
intent.putExtra(EXTRA_DEVICE_INFO, deviceInfo); // TODO: broadcast a clone of the info
return intent;
}
}

View File

@ -7,6 +7,7 @@ import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
@ -16,9 +17,9 @@ public class V1NotificationStrategy implements NotificationStrategy {
static final byte[] startVibrate = new byte[]{MiBandService.COMMAND_SEND_NOTIFICATION, 1};
static final byte[] stopVibrate = new byte[]{MiBandService.COMMAND_STOP_MOTOR_VIBRATE};
private final MiBandSupport support;
private final AbstractBTLEDeviceSupport support;
public V1NotificationStrategy(MiBandSupport support) {
public V1NotificationStrategy(AbstractBTLEDeviceSupport support) {
this.support = support;
}

View File

@ -3,14 +3,15 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
import android.bluetooth.BluetoothGattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.VibrationProfile;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
public class V2NotificationStrategy implements NotificationStrategy {
private final MiBandSupport support;
private final AbstractBTLEDeviceSupport support;
public V2NotificationStrategy(MiBandSupport support) {
public V2NotificationStrategy(AbstractBTLEDeviceSupport support) {
this.support = support;
}