From f2d865a28cc3b3b919277c3ff50e365f33847a8e Mon Sep 17 00:00:00 2001 From: Alan Date: Thu, 5 May 2016 10:12:47 +0200 Subject: [PATCH 01/60] Added osgibnd plugin to generate OSGI metadata Added osgibnd plugin to the build to generate relevant OSGI metadata fields in the final MANIFEST.MF file. Added also a version property to explicitly set the project version both in the final jar filename and in the OSGI metadata. --- build.gradle | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 67a4960..9369d94 100644 --- a/build.gradle +++ b/build.gradle @@ -7,8 +7,11 @@ * user guide available at http://gradle.org/docs/1.11/userguide/tutorial_java_projects.html */ -// Apply the java plugin to add support for Java -apply plugin: 'java' +plugins { + id "org.jruyi.osgibnd" version "0.5.0" +} + +// java plugin implied by the osgibnd plugin apply plugin: 'eclipse' apply plugin: 'idea' apply plugin: 'maven' @@ -18,10 +21,23 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 +version = '1.7.1' + repositories { mavenCentral() } dependencies { testCompile group: 'junit', name: 'junit', version: '4.11' -} \ No newline at end of file +} + +//OSGI specific entries that are not automatically generated +jar { + manifest { + attributes ( + 'Bundle-SymbolicName': project.group + project.name, + 'Bundle-Version': project.version, + 'Export-Package': 'com.movisens.smartgattlib,com.movisens.smartgattlib.characteristics' + ) + } +} From ad76a08bebd37295ee864ff13d20724c16bb66fb Mon Sep 17 00:00:00 2001 From: Robert Zetzsche Date: Fri, 13 May 2016 11:40:00 +0200 Subject: [PATCH 02/60] added uuids for user data service and user data characteristics age, gender, height, weight --- .../movisens/smartgattlib/Characteristic.java | 17 +++++++++++++---- .../java/com/movisens/smartgattlib/Service.java | 2 ++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/movisens/smartgattlib/Characteristic.java b/src/main/java/com/movisens/smartgattlib/Characteristic.java index 60ca674..9413e79 100644 --- a/src/main/java/com/movisens/smartgattlib/Characteristic.java +++ b/src/main/java/com/movisens/smartgattlib/Characteristic.java @@ -108,8 +108,13 @@ public class Characteristic { public static final UUID RECORD_ACCESS_CONTROL_POINT_TESTVERSION = new UUID((0x2A52L << 32) | 0x1000, GattUtils.leastSigBits); public static final UUID REMOVABLE = new UUID((0x2A3AL << 32) | 0x1000, GattUtils.leastSigBits); public static final UUID SERVICE_REQUIRED = new UUID((0x2A3BL << 32) | 0x1000, GattUtils.leastSigBits); - + public static final UUID AGE = new UUID((0x2A80L << 32) | 0x1000, GattUtils.leastSigBits); + public static final UUID GENDER = new UUID((0x2A8CL << 32) | 0x1000, GattUtils.leastSigBits); + public static final UUID WEIGHT = new UUID((0x2A98L << 32) | 0x1000, GattUtils.leastSigBits); + public static final UUID HEIGHT = new UUID((0x2A8EL << 32) | 0x1000, GattUtils.leastSigBits); + private static HashMap attributes = new HashMap(); + static { attributes.put(ALERT_CATEGORY_ID, "Alert Category ID"); attributes.put(ALERT_CATEGORY_ID_BIT_MASK, "Alert Category ID Bit Mask"); @@ -215,10 +220,14 @@ public class Characteristic { attributes.put(RECORD_ACCESS_CONTROL_POINT_TESTVERSION, "Record Access Control point (Test Version)"); attributes.put(REMOVABLE, "Removable"); attributes.put(SERVICE_REQUIRED, "Service Required"); - } + attributes.put(AGE, "Age of Participant"); + attributes.put(GENDER, "Gender of participant"); + attributes.put(WEIGHT, "Weight of participant"); + attributes.put(HEIGHT, "Height of participant"); + } - public static String lookup(UUID uuid, String defaultName) { - String name = attributes.get(uuid); + public static String lookup(UUID uuid, String defaultName) { + String name = attributes.get(uuid); return name == null ? defaultName : name; } } \ No newline at end of file diff --git a/src/main/java/com/movisens/smartgattlib/Service.java b/src/main/java/com/movisens/smartgattlib/Service.java index 3174f0a..33a2845 100644 --- a/src/main/java/com/movisens/smartgattlib/Service.java +++ b/src/main/java/com/movisens/smartgattlib/Service.java @@ -32,6 +32,7 @@ public class Service { public static final UUID LINK_LOSS_SERVICE_1_1 = new UUID((0x1803L << 32) | 0x1000, GattUtils.leastSigBits); public static final UUID NETWORK_AVAILABILITY_SERVICE = new UUID((0x180BL << 32) | 0x1000, GattUtils.leastSigBits); public static final UUID TX_POWER_SERVICE_1_1 = new UUID((0x1804L << 32) | 0x1000, GattUtils.leastSigBits); + public static final UUID USER_DATA_SERVICE = new UUID((0x181cL << 32) | 0x1000, GattUtils.leastSigBits); private static HashMap attributes = new HashMap(); static { @@ -63,6 +64,7 @@ public class Service { attributes.put(LINK_LOSS_SERVICE_1_1, "Link Loss Service 1.1"); attributes.put(NETWORK_AVAILABILITY_SERVICE, "Network Availability Service"); attributes.put(TX_POWER_SERVICE_1_1, "Tx Power Service 1.1"); + attributes.put(USER_DATA_SERVICE, "User Data Service"); } public static String lookup(UUID uuid, String defaultName) { From 5a7904bad6aeffb496ddb3c6a864f31e417bf8b5 Mon Sep 17 00:00:00 2001 From: Robert Zetzsche Date: Fri, 13 May 2016 14:55:43 +0200 Subject: [PATCH 03/60] added new characteristics to write data to userdata service --- .../smartgattlib/characteristics/Age.java | 21 +++++++++++++++ .../smartgattlib/characteristics/Gender.java | 26 +++++++++++++++++++ .../smartgattlib/characteristics/Height.java | 20 ++++++++++++++ .../smartgattlib/characteristics/Weight.java | 24 +++++++++++++++++ 4 files changed, 91 insertions(+) create mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/Age.java create mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/Gender.java create mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/Height.java create mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/Weight.java diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Age.java b/src/main/java/com/movisens/smartgattlib/characteristics/Age.java new file mode 100644 index 0000000..1d93ca7 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Age.java @@ -0,0 +1,21 @@ +package com.movisens.smartgattlib.characteristics; + +import com.movisens.smartgattlib.GattByteBuffer; + +public class Age { + private byte[] value; + + /** + * + * @param age + * in years (for example 35,5) + */ + public Age(short age) { + this.value = GattByteBuffer.allocate(4).putUint8(age).array(); + } + + public byte[] getValue() { + return value; + } + +} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java b/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java new file mode 100644 index 0000000..3784cae --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java @@ -0,0 +1,26 @@ +package com.movisens.smartgattlib.characteristics; + +import com.movisens.smartgattlib.GattByteBuffer; + +public class Gender { + public static enum Sex { + MALE((short) 0), FEMALE((short) 1); + + public final short value; + + private Sex(short value) { + this.value = value; + } + } + + private byte[] value; + + public Gender(Sex sex) { + this.value = GattByteBuffer.allocate(4).putUint8(sex.value).array(); + } + + public byte[] getValue() { + return value; + } + +} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Height.java b/src/main/java/com/movisens/smartgattlib/characteristics/Height.java new file mode 100644 index 0000000..9c6b3ec --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Height.java @@ -0,0 +1,20 @@ +package com.movisens.smartgattlib.characteristics; + +import com.movisens.smartgattlib.GattByteBuffer; + +public class Height { + private byte[] value; + + /** + * + * @param height + * in cm + */ + public Height(int height) { + this.value = GattByteBuffer.allocate(4).putUint16(height).array(); + } + + public byte[] getValue() { + return value; + } +} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java b/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java new file mode 100644 index 0000000..f0c5e24 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java @@ -0,0 +1,24 @@ +package com.movisens.smartgattlib.characteristics; + +import com.movisens.smartgattlib.GattByteBuffer; + +public class Weight { + private byte[] value; + + /** + * Constructor for new WeightCharacteristic + * + * @param weight + * in kg + */ + public Weight(float weight) { + // https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.weight.xml + float storeWeight = weight * 200; + this.value = GattByteBuffer.allocate(4).putUint16(Math.round(storeWeight)).array(); + } + + public byte[] getValue() { + return value; + } + +} From 1f56283a53784831482f72caecaff79cad74ff77 Mon Sep 17 00:00:00 2001 From: Robert Zetzsche Date: Sun, 29 May 2016 10:15:48 +0200 Subject: [PATCH 04/60] added new constructors for added values --- .../smartgattlib/characteristics/Age.java | 19 +++++++++++++++---- .../smartgattlib/characteristics/Gender.java | 19 +++++++++++++++++-- .../smartgattlib/characteristics/Height.java | 17 ++++++++++++++--- .../smartgattlib/characteristics/Weight.java | 18 +++++++++++++++--- 4 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Age.java b/src/main/java/com/movisens/smartgattlib/characteristics/Age.java index 1d93ca7..045d55b 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Age.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Age.java @@ -3,7 +3,8 @@ import com.movisens.smartgattlib.GattByteBuffer; public class Age { - private byte[] value; + private byte[] bytes; + private short value; /** * @@ -11,11 +12,21 @@ public class Age { * in years (for example 35,5) */ public Age(short age) { - this.value = GattByteBuffer.allocate(4).putUint8(age).array(); + this.value = age; + this.bytes = GattByteBuffer.allocate(4).putUint8(age).array(); } - public byte[] getValue() { - return value; + public Age(byte[] bytes) { + this.bytes = bytes; + this.value = GattByteBuffer.wrap(bytes).getUint8(); + } + + public byte[] getBytes() { + return bytes; + } + + public short getValue() { + return this.value; } } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java b/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java index 3784cae..5c26abd 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java @@ -8,19 +8,34 @@ public static enum Sex { public final short value; - private Sex(short value) { + Sex(short value) { this.value = value; } } private byte[] value; + private Sex sex; public Gender(Sex sex) { + this.sex = sex; this.value = GattByteBuffer.allocate(4).putUint8(sex.value).array(); } - public byte[] getValue() { + public Gender(byte[] bytes) { + this.value = bytes; + if (GattByteBuffer.wrap(bytes).getUint8() == 0) { + this.sex = Sex.MALE; + } else { + this.sex = Sex.FEMALE; + } + } + + public byte[] getBytes() { return value; } + public Sex getValue() { + return sex; + } + } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Height.java b/src/main/java/com/movisens/smartgattlib/characteristics/Height.java index 9c6b3ec..ddcb76c 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Height.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Height.java @@ -3,7 +3,8 @@ import com.movisens.smartgattlib.GattByteBuffer; public class Height { - private byte[] value; + private byte[] bytes; + private int value; /** * @@ -11,10 +12,20 @@ public class Height { * in cm */ public Height(int height) { - this.value = GattByteBuffer.allocate(4).putUint16(height).array(); + this.value = height; + this.bytes = GattByteBuffer.allocate(4).putUint16(height).array(); } - public byte[] getValue() { + public Height(byte[] bytes) { + this.bytes = bytes; + this.value = GattByteBuffer.wrap(bytes).getUint16(); + } + + public byte[] getBytes() { + return bytes; + } + + public int getValue() { return value; } } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java b/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java index f0c5e24..321a765 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java @@ -3,7 +3,8 @@ import com.movisens.smartgattlib.GattByteBuffer; public class Weight { - private byte[] value; + private byte[] bytes; + private float value; /** * Constructor for new WeightCharacteristic @@ -13,11 +14,22 @@ public class Weight { */ public Weight(float weight) { // https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.weight.xml + this.value = weight; float storeWeight = weight * 200; - this.value = GattByteBuffer.allocate(4).putUint16(Math.round(storeWeight)).array(); + this.bytes = GattByteBuffer.allocate(4).putUint16(Math.round(storeWeight)).array(); } - public byte[] getValue() { + public Weight(byte[] bytes) { + // https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.weight.xml + this.bytes = bytes; + this.value = GattByteBuffer.wrap(bytes).getUint16() / 200; + } + + public byte[] getBytes() { + return bytes; + } + + public float getValue() { return value; } From 802f4e86a793d2a5277ae7bbbf8ec69c023a38ef Mon Sep 17 00:00:00 2001 From: JStumpp Date: Mon, 30 May 2016 18:59:47 +0200 Subject: [PATCH 05/60] Moved example to test --- .../com/movisens/smartgattlib/Example.java | 37 ------------------- src/test/java/Example.java | 36 ++++++++++++++++++ 2 files changed, 36 insertions(+), 37 deletions(-) delete mode 100644 src/main/java/com/movisens/smartgattlib/Example.java create mode 100644 src/test/java/Example.java diff --git a/src/main/java/com/movisens/smartgattlib/Example.java b/src/main/java/com/movisens/smartgattlib/Example.java deleted file mode 100644 index 872f2f5..0000000 --- a/src/main/java/com/movisens/smartgattlib/Example.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.movisens.smartgattlib; - -import java.util.UUID; - -import com.movisens.smartgattlib.characteristics.HeartRateMeasurement; - - -public class Example { - - public static void main(String[] args) { - // onConnected - // TODO: iterate over available services - UUID serviceUuid = null;// service.getUuid(); - if (Service.HEART_RATE.equals(serviceUuid)) { - - // TODO: iterate over characteristics - UUID characteristicUuid = null;// characteristic.getUuid(); - if (Characteristic.HEART_RATE_MEASUREMENT.equals(characteristicUuid)) { - // TODO: Enable notification - //BluetoothGattDescriptor descriptor = characteristic.getDescriptor(Descriptor.CLIENT_CHARACTERISTIC_CONFIGURATION); - //descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - //mBluetoothGatt.writeDescriptor(descriptor); - } - }else{ - System.out.println("Found unused Service: " + Service.lookup(serviceUuid, "unknown")); - } - - // onCharacteristicChanged - UUID characteristicUuid = null;// characteristic.getUuid(); - if (Characteristic.HEART_RATE_MEASUREMENT.equals(characteristicUuid)) { - byte[] value = null;// characteristic.getValue(); - HeartRateMeasurement hrm = new HeartRateMeasurement(value); - hrm.getHr(); - hrm.getEe(); - } - } -} diff --git a/src/test/java/Example.java b/src/test/java/Example.java new file mode 100644 index 0000000..fe4c228 --- /dev/null +++ b/src/test/java/Example.java @@ -0,0 +1,36 @@ +import java.util.UUID; + +import com.movisens.smartgattlib.Characteristic; +import com.movisens.smartgattlib.Service; +import com.movisens.smartgattlib.characteristics.HeartRateMeasurement; + +public class Example { + + public static void main(String[] args) { + // onConnected + // TODO: iterate over available services + UUID serviceUuid = null;// service.getUuid(); + if (Service.HEART_RATE.equals(serviceUuid)) { + + // TODO: iterate over characteristics + UUID characteristicUuid = null;// characteristic.getUuid(); + if (Characteristic.HEART_RATE_MEASUREMENT.equals(characteristicUuid)) { + // TODO: Enable notification + //BluetoothGattDescriptor descriptor = characteristic.getDescriptor(Descriptor.CLIENT_CHARACTERISTIC_CONFIGURATION); + //descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + //mBluetoothGatt.writeDescriptor(descriptor); + } + } else { + System.out.println("Found unused Service: " + Service.lookup(serviceUuid, "unknown")); + } + + // onCharacteristicChanged + UUID characteristicUuid = null;// characteristic.getUuid(); + if (Characteristic.HEART_RATE_MEASUREMENT.equals(characteristicUuid)) { + byte[] value = null;// characteristic.getValue(); + HeartRateMeasurement hrm = new HeartRateMeasurement(value); + hrm.getHr(); + hrm.getEe(); + } + } +} From eb70187b21cff13473a63f322bf160c1e877665a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Stumpp?= Date: Thu, 30 Jun 2016 15:12:28 +0200 Subject: [PATCH 06/60] Fixed shield --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f49442a..8e3b8eb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ SmartGattLib ============ - + SmartGattLib is a Java library that simplifies the work with **Bluetooth SMART** devices (a.k.a. **Bluetooth Low Energy** in Bluetooth 4.0). It provides all UUIDs of the adopted [GATT specification](http://developer.bluetooth.org/gatt/Pages/default.aspx) and an convenient way to interpret the characteristics (e.g. Heart Rate, BatteryLevel). From b75e92974493dc0586902a06b9e995f521194e0d Mon Sep 17 00:00:00 2001 From: Robert Zetzsche Date: Tue, 23 May 2017 12:56:23 +0200 Subject: [PATCH 07/60] added abstract characteristics --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 +- settings.gradle | 1 + .../smartgattlib/characteristics/Age.java | 47 +++-- .../characteristics/BatteryLevel.java | 22 +-- .../characteristics/BodySensorLocation.java | 75 ++++---- .../CyclingSpeedCadenceMeasurement.java | 13 +- .../smartgattlib/characteristics/Gender.java | 70 ++++--- .../characteristics/HeartRateMeasurement.java | 171 +++++++++--------- .../smartgattlib/characteristics/Height.java | 39 ++-- .../ManufacturerNameString.java | 17 +- .../smartgattlib/characteristics/Weight.java | 43 ++--- .../definition/AbstractCharacteristic.java | 27 +++ .../AbstractReadOnlyCharacteristic.java | 28 +++ src/test/java/BatteryLevelTest.java | 2 +- src/test/java/WeightTest.java | 26 +++ 16 files changed, 325 insertions(+), 262 deletions(-) create mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java create mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java create mode 100644 src/test/java/WeightTest.java diff --git a/build.gradle b/build.gradle index 9369d94..8c3e0e1 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '1.7.1' +version = '2.0' repositories { mavenCentral() diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 508df78..eb44b30 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Mar 15 19:58:12 CET 2015 +#Fri May 19 18:31:12 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-all.zip diff --git a/settings.gradle b/settings.gradle index b805aab..c7f54e8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,3 +17,4 @@ include 'services:webservice' */ rootProject.name = 'SmartGattLib' + diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Age.java b/src/main/java/com/movisens/smartgattlib/characteristics/Age.java index 045d55b..c6fee4b 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Age.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Age.java @@ -1,32 +1,27 @@ package com.movisens.smartgattlib.characteristics; import com.movisens.smartgattlib.GattByteBuffer; +import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; -public class Age { - private byte[] bytes; - private short value; - - /** - * - * @param age - * in years (for example 35,5) - */ - public Age(short age) { - this.value = age; - this.bytes = GattByteBuffer.allocate(4).putUint8(age).array(); - } - - public Age(byte[] bytes) { - this.bytes = bytes; - this.value = GattByteBuffer.wrap(bytes).getUint8(); - } - - public byte[] getBytes() { - return bytes; - } - - public short getValue() { - return this.value; - } +public class Age extends AbstractCharacteristic { + + public Age(byte[] bytes) { + super(bytes); + } + + public Age(Short value) { + super(value); + } + + + @Override + protected Short getValueForBytes(byte[] bytes) { + return GattByteBuffer.wrap(bytes).getUint8(); + } + + @Override + protected byte[] getBytesForValue(Short value) { + return GattByteBuffer.allocate(4).putUint8(value).array(); + } } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java b/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java index c75d39f..e773e13 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java @@ -1,19 +1,17 @@ package com.movisens.smartgattlib.characteristics; import com.movisens.smartgattlib.GattByteBuffer; +import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; -public class BatteryLevel { - int level = -1; +public class BatteryLevel extends AbstractReadOnlyCharacteristic { - public BatteryLevel(byte[] value) { - level = GattByteBuffer.wrap(value).getUint8(); - } + public BatteryLevel(byte[] bytes) { + super(bytes); + } + + @Override + protected Short getValueForBytes(byte[] bytes) { + return GattByteBuffer.wrap(bytes).getUint8(); + } - /** - * @return The current charge level of a battery in %. 100% represents fully - * charged while 0% represents fully discharged. - */ - public int getBatteryLevel() { - return level; - } } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java b/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java index 193a993..7038583 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java @@ -1,46 +1,47 @@ package com.movisens.smartgattlib.characteristics; import com.movisens.smartgattlib.GattByteBuffer; +import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; -public class BodySensorLocation { - Location location = Location.Other; +public class BodySensorLocation extends AbstractReadOnlyCharacteristic { - public enum Location { - Other, Chest, Wrist, Finger, Hand, Ear_Lobe, Foot; - } + public BodySensorLocation(byte[] bytes) { + super(bytes); + } - public BodySensorLocation(byte[] value) { - int loc = GattByteBuffer.wrap(value).getUint8(); + public enum Location { + Other, Chest, Wrist, Finger, Hand, Ear_Lobe, Foot; + } - switch (loc) { - case 0: - location = Location.Other; - break; - case 1: - location = Location.Chest; - break; - case 2: - location = Location.Wrist; - break; - case 3: - location = Location.Finger; - break; - case 4: - location = Location.Hand; - break; - case 5: - location = Location.Ear_Lobe; - break; - case 6: - location = Location.Foot; - break; - } - } + @Override + protected Location getValueForBytes(byte[] bytes) { + Location location = Location.Other; + int loc = GattByteBuffer.wrap(bytes).getUint8(); + + switch (loc) { + case 0: + location = Location.Other; + break; + case 1: + location = Location.Chest; + break; + case 2: + location = Location.Wrist; + break; + case 3: + location = Location.Finger; + break; + case 4: + location = Location.Hand; + break; + case 5: + location = Location.Ear_Lobe; + break; + case 6: + location = Location.Foot; + break; + } + return location; + } - /** - * @return The current location of the sensor - */ - public Location getBodySensorLocation() { - return location; - } } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java b/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java index 398557f..3390abb 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java @@ -2,8 +2,9 @@ import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.GattUtils; +import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; -public class CyclingSpeedCadenceMeasurement { +public class CyclingSpeedCadenceMeasurement extends AbstractReadOnlyCharacteristic { public static final int MAX_CUMULATIVE_CRANK_REVS = 65535; public static final long MAX_CUMULATIVE_WHEEL_REVS = 4294967295L; @@ -16,10 +17,13 @@ public class CyclingSpeedCadenceMeasurement { private int cumulativeCrankRevolutions; private int lastCrankEventTime; - public CyclingSpeedCadenceMeasurement(byte[] value) { - super(); + public CyclingSpeedCadenceMeasurement(byte[] bytes) { + super(bytes); + } - GattByteBuffer bb = GattByteBuffer.wrap(value); + @Override + protected Number[] getValueForBytes(byte[] bytes) { + GattByteBuffer bb = GattByteBuffer.wrap(bytes); byte flags = bb.getInt8(); wheelRevPresent = wheelRevPresent(flags); @@ -34,6 +38,7 @@ public CyclingSpeedCadenceMeasurement(byte[] value) { cumulativeCrankRevolutions = bb.getUint16(); lastCrankEventTime = bb.getUint16(); } + return new Number[]{cumulativeWheelRevolutions, lastWheelEventTime, cumulativeCrankRevolutions, lastCrankEventTime}; } public boolean isWheelRevPresent() { diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java b/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java index 5c26abd..ccfe5a4 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java @@ -1,41 +1,39 @@ package com.movisens.smartgattlib.characteristics; import com.movisens.smartgattlib.GattByteBuffer; - -public class Gender { - public static enum Sex { - MALE((short) 0), FEMALE((short) 1); - - public final short value; - - Sex(short value) { - this.value = value; - } - } - - private byte[] value; - private Sex sex; - - public Gender(Sex sex) { - this.sex = sex; - this.value = GattByteBuffer.allocate(4).putUint8(sex.value).array(); - } - - public Gender(byte[] bytes) { - this.value = bytes; - if (GattByteBuffer.wrap(bytes).getUint8() == 0) { - this.sex = Sex.MALE; - } else { - this.sex = Sex.FEMALE; - } - } - - public byte[] getBytes() { - return value; - } - - public Sex getValue() { - return sex; - } +import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; + +public class Gender extends AbstractCharacteristic { + public Gender(byte[] bytes) { + super(bytes); + } + + public Gender(Sex value) { + super(value); + } + + public static enum Sex { + MALE((short) 0), FEMALE((short) 1); + + public final short value; + + Sex(short value) { + this.value = value; + } + } + + @Override + protected Sex getValueForBytes(byte[] bytes) { + if (GattByteBuffer.wrap(bytes).getUint8() == 0) { + return Sex.MALE; + } else { + return Sex.FEMALE; + } + } + + @Override + protected byte[] getBytesForValue(Sex value) { + return GattByteBuffer.allocate(4).putUint8(value.value).array(); + } } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java b/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java index 0f13bb3..a21a84e 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java @@ -4,92 +4,89 @@ import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.GattUtils; +import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; -public class HeartRateMeasurement { - - ArrayList rrIntervals = new ArrayList(); - int hrmval = 0; - int eeval = -1; - SensorWorn sensorWorn = SensorWorn.UNSUPPORTED; - - public enum SensorWorn { - UNSUPPORTED, WORN, NOT_WORN - } - - public HeartRateMeasurement(byte[] value) { - GattByteBuffer bb = GattByteBuffer.wrap(value); - byte flags = bb.getInt8(); - if (isHeartRateInUINT16(flags)) { - hrmval = bb.getUint16(); - } else { - hrmval = bb.getUint8(); - } - if (isWornStatusPresent(flags)) { - if (isSensorWorn(flags)) { - sensorWorn = SensorWorn.WORN; - } else { - sensorWorn = SensorWorn.NOT_WORN; - } - } - if (isEePresent(flags)) { - eeval = bb.getUint16(); - } - if (isRrIntPresent(flags)) { - while (bb.hasRemaining()) { - rrIntervals.add(bb.getUint16() / 1024F); - } - } - } - - private boolean isHeartRateInUINT16(byte flags) { - if ((flags & GattUtils.FIRST_BITMASK) != 0) - return true; - return false; - } - - private boolean isWornStatusPresent(byte flags) { - if ((flags & GattUtils.THIRD_BITMASK) != 0) - return true; - return false; - } - - private boolean isSensorWorn(byte flags) { - if ((flags & GattUtils.SECOND_BITMASK) != 0) - return true; - return false; - } - - private boolean isEePresent(byte flags) { - if ((flags & GattUtils.FOURTH_BITMASK) != 0) - return true; - return false; - } - - private boolean isRrIntPresent(byte flags) { - if ((flags & GattUtils.FIFTH_BITMASK) != 0) - return true; - return false; - } - - /** - * @return RR-Intervals. Units: seconds - */ - public ArrayList getRrInterval() { - return rrIntervals; - } - - public int getHr() { - return hrmval; - } - - /** - * @return Energy Expended, Units: kilo Joules - */ - public int getEe() { - return eeval; - } - - public SensorWorn getSensorWorn() { - return sensorWorn; - } +public class HeartRateMeasurement extends AbstractReadOnlyCharacteristic { + + ArrayList rrIntervals = new ArrayList(); + int hrmval = 0; + int eeval = -1; + SensorWorn sensorWorn = SensorWorn.UNSUPPORTED; + + public HeartRateMeasurement(byte[] bytes) { + super(bytes); + } + + public enum SensorWorn { + UNSUPPORTED, WORN, NOT_WORN + } + + @Override + protected Integer getValueForBytes(byte[] bytes) { + GattByteBuffer bb = GattByteBuffer.wrap(bytes); + byte flags = bb.getInt8(); + if (isHeartRateInUINT16(flags)) { + hrmval = bb.getUint16(); + } else { + hrmval = bb.getUint8(); + } + if (isWornStatusPresent(flags)) { + if (isSensorWorn(flags)) { + sensorWorn = SensorWorn.WORN; + } else { + sensorWorn = SensorWorn.NOT_WORN; + } + } + if (isEePresent(flags)) { + eeval = bb.getUint16(); + } + if (isRrIntPresent(flags)) { + while (bb.hasRemaining()) { + rrIntervals.add(bb.getUint16() / 1024F); + } + } + return hrmval; + } + + private boolean isHeartRateInUINT16(byte flags) { + return (flags & GattUtils.FIRST_BITMASK) != 0; + } + + private boolean isWornStatusPresent(byte flags) { + return (flags & GattUtils.THIRD_BITMASK) != 0; + } + + private boolean isSensorWorn(byte flags) { + return (flags & GattUtils.SECOND_BITMASK) != 0; + } + + private boolean isEePresent(byte flags) { + return (flags & GattUtils.FOURTH_BITMASK) != 0; + } + + private boolean isRrIntPresent(byte flags) { + return (flags & GattUtils.FIFTH_BITMASK) != 0; + } + + /** + * @return RR-Intervals. Units: seconds + */ + public ArrayList getRrInterval() { + return rrIntervals; + } + + public int getHr() { + return hrmval; + } + + /** + * @return Energy Expended, Units: kilo Joules + */ + public int getEe() { + return eeval; + } + + public SensorWorn getSensorWorn() { + return sensorWorn; + } } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Height.java b/src/main/java/com/movisens/smartgattlib/characteristics/Height.java index ddcb76c..3f7f271 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Height.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Height.java @@ -1,31 +1,26 @@ package com.movisens.smartgattlib.characteristics; import com.movisens.smartgattlib.GattByteBuffer; +import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; -public class Height { - private byte[] bytes; - private int value; +public class Height extends AbstractCharacteristic { - /** - * - * @param height - * in cm - */ - public Height(int height) { - this.value = height; - this.bytes = GattByteBuffer.allocate(4).putUint16(height).array(); - } + public Height(byte[] bytes) { + super(bytes); + } - public Height(byte[] bytes) { - this.bytes = bytes; - this.value = GattByteBuffer.wrap(bytes).getUint16(); - } + public Height(Integer value) { + super(value); + } - public byte[] getBytes() { - return bytes; - } + @Override + protected Integer getValueForBytes(byte[] bytes) { + return GattByteBuffer.wrap(bytes).getUint16(); + } + + @Override + protected byte[] getBytesForValue(Integer value) { + return GattByteBuffer.allocate(4).putUint16(value).array(); + } - public int getValue() { - return value; - } } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java b/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java index ab7493c..3d505b3 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java @@ -1,15 +1,16 @@ package com.movisens.smartgattlib.characteristics; import com.movisens.smartgattlib.GattByteBuffer; +import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; -public class ManufacturerNameString { - String content = ""; +public class ManufacturerNameString extends AbstractReadOnlyCharacteristic { - public ManufacturerNameString(byte[] value) { - content = GattByteBuffer.wrap(value).getString(); - } + public ManufacturerNameString(byte[] bytes) { + super(bytes); + } - public String getManufacturerNameString() { - return content; - } + @Override + protected String getValueForBytes(byte[] bytes) { + return GattByteBuffer.wrap(bytes).getString(); + } } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java b/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java index 321a765..c5d63f8 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java @@ -1,36 +1,27 @@ package com.movisens.smartgattlib.characteristics; import com.movisens.smartgattlib.GattByteBuffer; +import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; -public class Weight { - private byte[] bytes; - private float value; +public class Weight extends AbstractCharacteristic { - /** - * Constructor for new WeightCharacteristic - * - * @param weight - * in kg - */ - public Weight(float weight) { - // https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.weight.xml - this.value = weight; - float storeWeight = weight * 200; - this.bytes = GattByteBuffer.allocate(4).putUint16(Math.round(storeWeight)).array(); - } + public Weight(byte[] bytes) { + super(bytes); + } - public Weight(byte[] bytes) { - // https://developer.bluetooth.org/gatt/characteristics/Pages/CharacteristicViewer.aspx?u=org.bluetooth.characteristic.weight.xml - this.bytes = bytes; - this.value = GattByteBuffer.wrap(bytes).getUint16() / 200; - } + public Weight(Float value) { + super(value); + } - public byte[] getBytes() { - return bytes; - } + @Override + protected Float getValueForBytes(byte[] bytes) { + return GattByteBuffer.wrap(bytes).getUint16() / 200f; + } - public float getValue() { - return value; - } + @Override + protected byte[] getBytesForValue(Float value) { + float storeWeight = value * 200; + return GattByteBuffer.allocate(4).putUint16(Math.round(storeWeight)).array(); + } } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java new file mode 100644 index 0000000..9a3b685 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java @@ -0,0 +1,27 @@ +package com.movisens.smartgattlib.characteristics.definition; + +/** + * Created by Robert Zetzsche on 07.03.2017. + */ + +public abstract class AbstractCharacteristic extends AbstractReadOnlyCharacteristic { + protected byte[] bytes; + + public AbstractCharacteristic(byte[] bytes) { + super(bytes); + this.bytes = bytes; + } + + public AbstractCharacteristic(T value) { + this.value = value; + this.bytes = getBytesForValue(value); + } + + public byte[] getBytes() { + return bytes; + } + + protected abstract T getValueForBytes(byte[] bytes); + + protected abstract byte[] getBytesForValue(T value); +} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java new file mode 100644 index 0000000..00cffd8 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java @@ -0,0 +1,28 @@ +package com.movisens.smartgattlib.characteristics.definition; + +/** + * Created by Robert Zetzsche on 07.03.2017. + */ + +public abstract class AbstractReadOnlyCharacteristic { + protected T value; + + AbstractReadOnlyCharacteristic() { + } + + public AbstractReadOnlyCharacteristic(byte[] bytes) { + this.value = getValueForBytes(bytes); + } + + public T getValue() { + return value; + } + + public boolean isValid() { + return true; + } + + protected abstract T getValueForBytes(byte[] bytes); + + +} diff --git a/src/test/java/BatteryLevelTest.java b/src/test/java/BatteryLevelTest.java index 8e6bd04..88a82fc 100644 --- a/src/test/java/BatteryLevelTest.java +++ b/src/test/java/BatteryLevelTest.java @@ -9,6 +9,6 @@ public class BatteryLevelTest { @Test public void testBatteryLevel() { byte[] testValue = GattUtils.hexStringToByteArray("2C"); BatteryLevel batteryLevel = new BatteryLevel(testValue); - assertTrue("Battery level should be 44", batteryLevel.getBatteryLevel() == 44); + assertTrue("Battery level should be 44", batteryLevel.getValue() == 44); } } diff --git a/src/test/java/WeightTest.java b/src/test/java/WeightTest.java new file mode 100644 index 0000000..276cd44 --- /dev/null +++ b/src/test/java/WeightTest.java @@ -0,0 +1,26 @@ +import com.movisens.smartgattlib.GattByteBuffer; +import com.movisens.smartgattlib.characteristics.Weight; +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.assertTrue; + +/** + * Created by robert.zetzsche on 17.05.2017. + */ +public class WeightTest { + @Test + public void testWeight() { + Float weightFloat = 12.5f; + byte[] bytes = GattByteBuffer.allocate(4).putUint16(Math.round(weightFloat * 200)).array(); + Weight weight = new Weight(weightFloat); + Weight weightBytes = new Weight(bytes); + assertTrue(weight.getValue().equals(weightFloat)); + assertTrue(weightBytes.getValue().equals(weightFloat)); + assertTrue(weightBytes.getValue().equals(weight.getValue())); + assertTrue(Arrays.equals(weight.getBytes(), bytes)); + assertTrue(Arrays.equals(weightBytes.getBytes(), bytes)); + assertTrue(Arrays.equals(weight.getBytes(), weightBytes.getBytes())); + } +} From 3f3985a5d1e1c7133bbeebd3ea58436265952a9c Mon Sep 17 00:00:00 2001 From: rzetzsche Date: Tue, 23 May 2017 13:00:16 +0200 Subject: [PATCH 08/60] Update build.gradle --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 8c3e0e1..e9e0e82 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '2.0' +version = '2.0.0' repositories { mavenCentral() From 7de8d49e5af7b1623a29f683794a75bb1906b6b9 Mon Sep 17 00:00:00 2001 From: Robert Zetzsche Date: Tue, 13 Jun 2017 15:20:49 +0200 Subject: [PATCH 09/60] added smartgattlib --- .../definition/AbstractReadOnlyCharacteristic.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java index 00cffd8..3b0407f 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java @@ -24,5 +24,8 @@ public boolean isValid() { protected abstract T getValueForBytes(byte[] bytes); - + @Override + public String toString() { + return getClass().getSimpleName() + "=" + getValue(); + } } From 1face6f6de5c9856c4eb2acbdd0e8debcb49af63 Mon Sep 17 00:00:00 2001 From: Robert Zetzsche Date: Tue, 13 Jun 2017 15:44:36 +0200 Subject: [PATCH 10/60] added getuuid --- .../movisens/smartgattlib/characteristics/Age.java | 5 +++-- .../smartgattlib/characteristics/BatteryLevel.java | 3 ++- .../characteristics/BodySensorLocation.java | 3 ++- .../CyclingSpeedCadenceMeasurement.java | 3 ++- .../smartgattlib/characteristics/Gender.java | 5 +++-- .../characteristics/HeartRateMeasurement.java | 3 ++- .../smartgattlib/characteristics/Height.java | 5 +++-- .../characteristics/ManufacturerNameString.java | 3 ++- .../smartgattlib/characteristics/Weight.java | 5 +++-- .../definition/AbstractCharacteristic.java | 9 ++++++--- .../definition/AbstractReadOnlyCharacteristic.java | 14 ++++++++++++-- 11 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Age.java b/src/main/java/com/movisens/smartgattlib/characteristics/Age.java index c6fee4b..c32bcf7 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Age.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Age.java @@ -1,16 +1,17 @@ package com.movisens.smartgattlib.characteristics; +import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; public class Age extends AbstractCharacteristic { public Age(byte[] bytes) { - super(bytes); + super(bytes, Characteristic.AGE); } public Age(Short value) { - super(value); + super(value, Characteristic.AGE); } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java b/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java index e773e13..7e81bfb 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java @@ -1,12 +1,13 @@ package com.movisens.smartgattlib.characteristics; +import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; public class BatteryLevel extends AbstractReadOnlyCharacteristic { public BatteryLevel(byte[] bytes) { - super(bytes); + super(bytes, Characteristic.BATTERY_LEVEL); } @Override diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java b/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java index 7038583..bf852bd 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java @@ -1,12 +1,13 @@ package com.movisens.smartgattlib.characteristics; +import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; public class BodySensorLocation extends AbstractReadOnlyCharacteristic { public BodySensorLocation(byte[] bytes) { - super(bytes); + super(bytes, Characteristic.BODY_SENSOR_LOCATION); } public enum Location { diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java b/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java index 3390abb..c9e7557 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java @@ -1,5 +1,6 @@ package com.movisens.smartgattlib.characteristics; +import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.GattUtils; import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; @@ -18,7 +19,7 @@ public class CyclingSpeedCadenceMeasurement extends AbstractReadOnlyCharacterist private int lastCrankEventTime; public CyclingSpeedCadenceMeasurement(byte[] bytes) { - super(bytes); + super(bytes, Characteristic.CYCLING_POWER_MEASUREMENT); } @Override diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java b/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java index ccfe5a4..c2dfd1d 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java @@ -1,15 +1,16 @@ package com.movisens.smartgattlib.characteristics; +import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; public class Gender extends AbstractCharacteristic { public Gender(byte[] bytes) { - super(bytes); + super(bytes, Characteristic.GENDER); } public Gender(Sex value) { - super(value); + super(value, Characteristic.GENDER); } public static enum Sex { diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java b/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java index a21a84e..10adf20 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java @@ -2,6 +2,7 @@ import java.util.ArrayList; +import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.GattUtils; import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; @@ -14,7 +15,7 @@ public class HeartRateMeasurement extends AbstractReadOnlyCharacteristic { public Height(byte[] bytes) { - super(bytes); + super(bytes, Characteristic.HEIGHT); } public Height(Integer value) { - super(value); + super(value, Characteristic.HEIGHT); } @Override diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java b/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java index 3d505b3..af8e903 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java @@ -1,12 +1,13 @@ package com.movisens.smartgattlib.characteristics; +import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; public class ManufacturerNameString extends AbstractReadOnlyCharacteristic { public ManufacturerNameString(byte[] bytes) { - super(bytes); + super(bytes, Characteristic.MANUFACTURER_NAME_STRING); } @Override diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java b/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java index c5d63f8..6f478ff 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java @@ -1,16 +1,17 @@ package com.movisens.smartgattlib.characteristics; +import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; public class Weight extends AbstractCharacteristic { public Weight(byte[] bytes) { - super(bytes); + super(bytes, Characteristic.WEIGHT); } public Weight(Float value) { - super(value); + super(value, Characteristic.WEIGHT); } @Override diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java index 9a3b685..ce3e963 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java @@ -1,5 +1,7 @@ package com.movisens.smartgattlib.characteristics.definition; +import java.util.UUID; + /** * Created by Robert Zetzsche on 07.03.2017. */ @@ -7,12 +9,13 @@ public abstract class AbstractCharacteristic extends AbstractReadOnlyCharacteristic { protected byte[] bytes; - public AbstractCharacteristic(byte[] bytes) { - super(bytes); + protected AbstractCharacteristic(byte[] bytes, UUID uuid) { + super(bytes, uuid); this.bytes = bytes; } - public AbstractCharacteristic(T value) { + protected AbstractCharacteristic(T value, UUID uuid) { + super(uuid); this.value = value; this.bytes = getBytesForValue(value); } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java index 3b0407f..2ce0711 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java @@ -1,5 +1,7 @@ package com.movisens.smartgattlib.characteristics.definition; +import java.util.UUID; + /** * Created by Robert Zetzsche on 07.03.2017. */ @@ -7,17 +9,25 @@ public abstract class AbstractReadOnlyCharacteristic { protected T value; - AbstractReadOnlyCharacteristic() { + protected final UUID uuid; + + protected AbstractReadOnlyCharacteristic(UUID uuid) { + this.uuid = uuid; } - public AbstractReadOnlyCharacteristic(byte[] bytes) { + protected AbstractReadOnlyCharacteristic(byte[] bytes, UUID uuid) { this.value = getValueForBytes(bytes); + this.uuid = uuid; } public T getValue() { return value; } + public UUID getUuid() { + return uuid; + } + public boolean isValid() { return true; } From 122f2bda2cd482e4482200f15a808add1e8df035 Mon Sep 17 00:00:00 2001 From: Robert Zetzsche Date: Tue, 13 Jun 2017 15:48:51 +0200 Subject: [PATCH 11/60] added packageprivacy to abstractreadonly characteristic --- .../definition/AbstractReadOnlyCharacteristic.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java index 2ce0711..4e5482d 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java @@ -11,7 +11,7 @@ public abstract class AbstractReadOnlyCharacteristic { protected final UUID uuid; - protected AbstractReadOnlyCharacteristic(UUID uuid) { + AbstractReadOnlyCharacteristic(UUID uuid) { this.uuid = uuid; } From dadf5a8094b6eabead9ddf9a675b5f663cd623bd Mon Sep 17 00:00:00 2001 From: Robert Zetzsche Date: Tue, 13 Jun 2017 15:50:52 +0200 Subject: [PATCH 12/60] updated gitignore --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index dece6c5..a35cfc3 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,6 @@ dist/ /.idea /.gradle /build -/SmartGattLib.iml -/SmartGattLib.ipr -/SmartGattLib.iws +*.iml +*.ipr +*.iws From a008365be6ca12d4b0d4c32ddaffd38ef853f19c Mon Sep 17 00:00:00 2001 From: Robert Zetzsche Date: Tue, 13 Jun 2017 15:52:46 +0200 Subject: [PATCH 13/60] added new version number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e9e0e82..010a343 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '2.0.0' +version = '2.1.0' repositories { mavenCentral() From d8404bf2d87302623890f351a7db69eff14e8653 Mon Sep 17 00:00:00 2001 From: Robert Zetzsche Date: Tue, 13 Jun 2017 16:07:13 +0200 Subject: [PATCH 14/60] reverted uuid changes --- .../movisens/smartgattlib/characteristics/Age.java | 5 ++--- .../smartgattlib/characteristics/BatteryLevel.java | 3 +-- .../characteristics/BodySensorLocation.java | 3 +-- .../CyclingSpeedCadenceMeasurement.java | 3 +-- .../smartgattlib/characteristics/Gender.java | 5 ++--- .../characteristics/HeartRateMeasurement.java | 3 +-- .../smartgattlib/characteristics/Height.java | 5 ++--- .../characteristics/ManufacturerNameString.java | 3 +-- .../smartgattlib/characteristics/Weight.java | 5 ++--- .../definition/AbstractCharacteristic.java | 9 +++------ .../definition/AbstractReadOnlyCharacteristic.java | 14 ++------------ 11 files changed, 18 insertions(+), 40 deletions(-) diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Age.java b/src/main/java/com/movisens/smartgattlib/characteristics/Age.java index c32bcf7..c6fee4b 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Age.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Age.java @@ -1,17 +1,16 @@ package com.movisens.smartgattlib.characteristics; -import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; public class Age extends AbstractCharacteristic { public Age(byte[] bytes) { - super(bytes, Characteristic.AGE); + super(bytes); } public Age(Short value) { - super(value, Characteristic.AGE); + super(value); } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java b/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java index 7e81bfb..e773e13 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java @@ -1,13 +1,12 @@ package com.movisens.smartgattlib.characteristics; -import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; public class BatteryLevel extends AbstractReadOnlyCharacteristic { public BatteryLevel(byte[] bytes) { - super(bytes, Characteristic.BATTERY_LEVEL); + super(bytes); } @Override diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java b/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java index bf852bd..7038583 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java @@ -1,13 +1,12 @@ package com.movisens.smartgattlib.characteristics; -import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; public class BodySensorLocation extends AbstractReadOnlyCharacteristic { public BodySensorLocation(byte[] bytes) { - super(bytes, Characteristic.BODY_SENSOR_LOCATION); + super(bytes); } public enum Location { diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java b/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java index c9e7557..3390abb 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java @@ -1,6 +1,5 @@ package com.movisens.smartgattlib.characteristics; -import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.GattUtils; import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; @@ -19,7 +18,7 @@ public class CyclingSpeedCadenceMeasurement extends AbstractReadOnlyCharacterist private int lastCrankEventTime; public CyclingSpeedCadenceMeasurement(byte[] bytes) { - super(bytes, Characteristic.CYCLING_POWER_MEASUREMENT); + super(bytes); } @Override diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java b/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java index c2dfd1d..ccfe5a4 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java @@ -1,16 +1,15 @@ package com.movisens.smartgattlib.characteristics; -import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; public class Gender extends AbstractCharacteristic { public Gender(byte[] bytes) { - super(bytes, Characteristic.GENDER); + super(bytes); } public Gender(Sex value) { - super(value, Characteristic.GENDER); + super(value); } public static enum Sex { diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java b/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java index 10adf20..a21a84e 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java @@ -2,7 +2,6 @@ import java.util.ArrayList; -import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.GattUtils; import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; @@ -15,7 +14,7 @@ public class HeartRateMeasurement extends AbstractReadOnlyCharacteristic { public Height(byte[] bytes) { - super(bytes, Characteristic.HEIGHT); + super(bytes); } public Height(Integer value) { - super(value, Characteristic.HEIGHT); + super(value); } @Override diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java b/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java index af8e903..3d505b3 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java @@ -1,13 +1,12 @@ package com.movisens.smartgattlib.characteristics; -import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; public class ManufacturerNameString extends AbstractReadOnlyCharacteristic { public ManufacturerNameString(byte[] bytes) { - super(bytes, Characteristic.MANUFACTURER_NAME_STRING); + super(bytes); } @Override diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java b/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java index 6f478ff..c5d63f8 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java @@ -1,17 +1,16 @@ package com.movisens.smartgattlib.characteristics; -import com.movisens.smartgattlib.Characteristic; import com.movisens.smartgattlib.GattByteBuffer; import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; public class Weight extends AbstractCharacteristic { public Weight(byte[] bytes) { - super(bytes, Characteristic.WEIGHT); + super(bytes); } public Weight(Float value) { - super(value, Characteristic.WEIGHT); + super(value); } @Override diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java index ce3e963..9a3b685 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java @@ -1,7 +1,5 @@ package com.movisens.smartgattlib.characteristics.definition; -import java.util.UUID; - /** * Created by Robert Zetzsche on 07.03.2017. */ @@ -9,13 +7,12 @@ public abstract class AbstractCharacteristic extends AbstractReadOnlyCharacteristic { protected byte[] bytes; - protected AbstractCharacteristic(byte[] bytes, UUID uuid) { - super(bytes, uuid); + public AbstractCharacteristic(byte[] bytes) { + super(bytes); this.bytes = bytes; } - protected AbstractCharacteristic(T value, UUID uuid) { - super(uuid); + public AbstractCharacteristic(T value) { this.value = value; this.bytes = getBytesForValue(value); } diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java index 4e5482d..3b0407f 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java +++ b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java @@ -1,7 +1,5 @@ package com.movisens.smartgattlib.characteristics.definition; -import java.util.UUID; - /** * Created by Robert Zetzsche on 07.03.2017. */ @@ -9,25 +7,17 @@ public abstract class AbstractReadOnlyCharacteristic { protected T value; - protected final UUID uuid; - - AbstractReadOnlyCharacteristic(UUID uuid) { - this.uuid = uuid; + AbstractReadOnlyCharacteristic() { } - protected AbstractReadOnlyCharacteristic(byte[] bytes, UUID uuid) { + public AbstractReadOnlyCharacteristic(byte[] bytes) { this.value = getValueForBytes(bytes); - this.uuid = uuid; } public T getValue() { return value; } - public UUID getUuid() { - return uuid; - } - public boolean isValid() { return true; } From e37f34cdc2addafb1602b6ad263da2340d4bf0de Mon Sep 17 00:00:00 2001 From: "juergen.stumpp" Date: Tue, 7 Nov 2017 15:39:55 +0100 Subject: [PATCH 15/60] Refactored SmartGattLib with breaking API changes. --- CHANGELOG.md | 29 ++ README.md | 59 ++- build.gradle | 12 +- .../smartgattlib/Characteristics.java | 458 ++++++++++++++++++ .../com/movisens/smartgattlib/Services.java | 106 ++++ .../movisens/smartgattlib/attributes/Age.java | 51 ++ .../smartgattlib/attributes/Appearance.java | 43 ++ .../smartgattlib/attributes/BatteryLevel.java | 43 ++ .../smartgattlib/attributes/DateOfBirth.java | 79 +++ .../smartgattlib/attributes/DeviceName.java | 43 ++ .../smartgattlib/attributes/EnumGender.java | 49 ++ .../attributes/FirmwareRevisionString.java | 43 ++ .../smartgattlib/attributes/Gender.java | 51 ++ .../smartgattlib/attributes/Height.java | 51 ++ .../attributes/ManufacturerNameString.java | 43 ++ .../attributes/ModelNumberString.java | 43 ++ .../attributes/SerialNumberString.java | 43 ++ .../smartgattlib/attributes/Weight.java | 51 ++ .../movisens/smartgattlib/Characteristic.java | 233 --------- .../com/movisens/smartgattlib/Descriptor.java | 40 -- .../movisens/smartgattlib/GattByteBuffer.java | 185 ------- .../com/movisens/smartgattlib/Service.java | 74 --- .../attributes/BodySensorLocation.java | 71 +++ .../CyclingSpeedCadenceMeasurement.java | 34 +- .../attributes/DefaultAttribute.java | 43 ++ .../attributes/HeartRateMeasurement.java | 110 +++++ .../smartgattlib/characteristics/Age.java | 27 -- .../characteristics/BatteryLevel.java | 17 - .../characteristics/BodySensorLocation.java | 47 -- .../smartgattlib/characteristics/Gender.java | 39 -- .../characteristics/HeartRateMeasurement.java | 92 ---- .../smartgattlib/characteristics/Height.java | 26 - .../ManufacturerNameString.java | 16 - .../smartgattlib/characteristics/Weight.java | 27 -- .../definition/AbstractCharacteristic.java | 27 -- .../AbstractReadOnlyCharacteristic.java | 31 -- .../CharacteristicDeclaration.java | 89 ++++ .../ClientCharacteristicConfiguration.java | 42 ++ .../helper/AbstractAttribute.java | 19 + .../helper/AbstractReadAttribute.java | 19 + .../helper/AbstractReadWriteAttribute.java | 18 + .../helper/AbstractWriteAttribute.java | 18 + .../smartgattlib/helper/Characteristic.java | 35 ++ .../smartgattlib/helper/GattByteBuffer.java | 261 ++++++++++ .../movisens/smartgattlib/helper/Service.java | 11 + .../smartgattlib/helper/UuidObject.java | 49 ++ .../smartgattlib/helper/UuidObjectMap.java | 20 + src/test/java/BatteryLevelTest.java | 5 +- .../CyclingSpeedCadenceMeasurementTest.java | 6 +- src/test/java/Example.java | 42 +- src/test/java/HeartRateMeasurementTest.java | 6 +- src/test/java/WeightTest.java | 22 +- 52 files changed, 2141 insertions(+), 957 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 src-gen/main/java/com/movisens/smartgattlib/Characteristics.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/Services.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/attributes/EnumGender.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java create mode 100644 src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java delete mode 100644 src/main/java/com/movisens/smartgattlib/Characteristic.java delete mode 100644 src/main/java/com/movisens/smartgattlib/Descriptor.java delete mode 100644 src/main/java/com/movisens/smartgattlib/GattByteBuffer.java delete mode 100644 src/main/java/com/movisens/smartgattlib/Service.java create mode 100644 src/main/java/com/movisens/smartgattlib/attributes/BodySensorLocation.java rename src/main/java/com/movisens/smartgattlib/{characteristics => attributes}/CyclingSpeedCadenceMeasurement.java (76%) create mode 100644 src/main/java/com/movisens/smartgattlib/attributes/DefaultAttribute.java create mode 100644 src/main/java/com/movisens/smartgattlib/attributes/HeartRateMeasurement.java delete mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/Age.java delete mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java delete mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java delete mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/Gender.java delete mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java delete mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/Height.java delete mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java delete mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/Weight.java delete mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java delete mode 100644 src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java create mode 100644 src/main/java/com/movisens/smartgattlib/declarations/CharacteristicDeclaration.java create mode 100644 src/main/java/com/movisens/smartgattlib/descriptors/ClientCharacteristicConfiguration.java create mode 100644 src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java create mode 100644 src/main/java/com/movisens/smartgattlib/helper/AbstractReadAttribute.java create mode 100644 src/main/java/com/movisens/smartgattlib/helper/AbstractReadWriteAttribute.java create mode 100644 src/main/java/com/movisens/smartgattlib/helper/AbstractWriteAttribute.java create mode 100644 src/main/java/com/movisens/smartgattlib/helper/Characteristic.java create mode 100644 src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java create mode 100644 src/main/java/com/movisens/smartgattlib/helper/Service.java create mode 100644 src/main/java/com/movisens/smartgattlib/helper/UuidObject.java create mode 100644 src/main/java/com/movisens/smartgattlib/helper/UuidObjectMap.java diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4e832c2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,29 @@ + +# [3.0.0](https://github.com/movisens/SmartGattLib/compare/v2.1...v3.0) (2017-11-07) + +This release comes with a significant api change to simplify + +### Upgrade Instructions + +* replace ```com.movisens.smartgattlib.Service``` with ```com.movisens.smartgattlib.Services``` +* replace ```com.movisens.smartgattlib.Characteristic``` with ```com.movisens.smartgattlib.Characteristics``` + +It is now possible to parse Characteristics with: +``` java +AbstractAttribute a = Characteristics.lookup(uuid).createAttribute(data); +if (a instanceof HeartRateMeasurement) { + HeartRateMeasurement heartRateMeasurement = ((HeartRateMeasurement) a); + heartRateMeasurement.getHr(); + heartRateMeasurement.getEe(); +} else if (a instanceof DefaultAttribute) { + System.err.println("characteristic for " + uuid + " is unknown"); +} else { + System.out.println("unused characteristic " + a.getCharacteristic().getName()); +} +``` + +It is also possible to write Characteristics and convert them to bytes: +``` java +AbstractAttribute aa = new Weight(12.3); +// TODO: Write aa.getBytes() to aa.getCharacteristic().getUuid(); +``` \ No newline at end of file diff --git a/README.md b/README.md index 8e3b8eb..f546c86 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,7 @@ SmartGattLib is a Java library that simplifies the work with **Bluetooth SMART** The library has **no dependencies** and can be use with **every Bluetooth SMART stack** e.g.: * [Android API Level 18](http://developer.android.com/guide/topics/connectivity/bluetooth-le.html) - * [Samsung BLE SDK](http://developer.samsung.com/ble) - * [HTC OpenSense BLE API](http://www.htcdev.com/devcenter/opensense-sdk/partner-apis/bluetooth-low-energy/) - * Motorola (seems obsolete) + * [RxAndroidBle](https://github.com/Polidea/RxAndroidBle) ### Integration ### Working with Bluetooth SMART devices is usually done in the following way: @@ -41,7 +39,7 @@ Example Android project with SmartGattLib available [here](https://github.com/mo maven { url "https://jitpack.io/" } } dependencies { - compile 'com.github.movisens:SmartGattLib:1.7' + compile 'com.github.movisens:SmartGattLib:3.0' } ``` or download the latest .jar file from the [releases](https://github.com/movisens/SmartGattLib/releases) page and place it in your Android app’s libs/ folder. @@ -50,34 +48,47 @@ Example Android project with SmartGattLib available [here](https://github.com/mo ### Example Usage ### ```java import com.movisens.smartgattlib.*; +import com.movisens.smartgattlib.attributes.*; +import com.movisens.smartgattlib.helper.*; // onConnected -//TODO: iterate over available services -UUID serviceUuid = service.getUuid(); -if (Service.HEART_RATE.equals(serviceUuid)) { // Identify Service - //TODO: iterate over characteristics - UUID characteristicUuid = characteristic.getUuid(); - if (Characteristic.HEART_RATE_MEASUREMENT.equals(characteristicUuid)) { // Identify Characteristic - // TODO: Enable notification e.g. for Android API 18: - // BluetoothGattDescriptor descriptor = characteristic.getDescriptor(Descriptor.CLIENT_CHARACTERISTIC_CONFIGURATION); - // descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - // mBluetoothGatt.writeDescriptor(descriptor); - } -}else{ - System.out.println("Found unused Service: " + Service.lookup(serviceUuid, "unknown")); +// TODO: iterate over available services +UUID serviceUuid = null;// service.getUuid(); +if (Services.HEART_RATE.getUuid().equals(serviceUuid)) { + + // TODO: iterate over characteristics + UUID characteristicUuid = null;// characteristic.getUuid(); + if (Characteristics.HEART_RATE_MEASUREMENT.getUuid().equals(characteristicUuid)) { + // TODO: Enable notification e.g. for Android API 18: + // BluetoothGattDescriptor descriptor = characteristic.getDescriptor(Descriptor.CLIENT_CHARACTERISTIC_CONFIGURATION); + // descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + // mBluetoothGatt.writeDescriptor(descriptor); + } +} else { + System.out.println("Found unused Service: " + Services.lookup(serviceUuid)); } // onCharacteristicChanged -UUID characteristicUuid = characteristic.getUuid(); -if (Characteristic.HEART_RATE_MEASUREMENT.equals(characteristicUuid)) { // Identify Characteristic - byte[] value = characteristic.getValue(); - HeartRateMeasurement hrm = new HeartRateMeasurement(value); // Interpret Characteristic - System.out.println("HR: " + hrm.getHr() + "bpm"); - System.out.println("EE: " + hrm.getEe() + "kJ"); +UUID uuid = null;// characteristic.getUuid(); +byte[] data = null;// characteristic.getValue(); + +AbstractAttribute a = Characteristics.lookup(uuid).createAttribute(data); +if (a instanceof HeartRateMeasurement) { + HeartRateMeasurement heartRateMeasurement = ((HeartRateMeasurement) a); + heartRateMeasurement.getHr(); + heartRateMeasurement.getEe(); +} else if (a instanceof DefaultAttribute) { + System.err.println("characteristic for " + uuid + " is unknown"); +} else { + System.out.println("unused characteristic " + a.getCharacteristic().getName()); } + +// write Attribute +AbstractAttribute aa = new Weight(12.3); +// TODO: Write aa.getBytes() to aa.getCharacteristic().getUuid(); ``` ### License ### -Copyright 2013 movisens GmbH +Copyright 2017 movisens GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/build.gradle b/build.gradle index 010a343..4b5bad4 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '2.1.0' +version = '3.0.0' repositories { mavenCentral() @@ -31,13 +31,21 @@ dependencies { testCompile group: 'junit', name: 'junit', version: '4.11' } +sourceSets { + main { + java { + srcDir('src-gen/main/java') + } + } +} + //OSGI specific entries that are not automatically generated jar { manifest { attributes ( 'Bundle-SymbolicName': project.group + project.name, 'Bundle-Version': project.version, - 'Export-Package': 'com.movisens.smartgattlib,com.movisens.smartgattlib.characteristics' + 'Export-Package': 'com.movisens.smartgattlib' ) } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java new file mode 100644 index 0000000..32bf438 --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java @@ -0,0 +1,458 @@ +package com.movisens.smartgattlib; + +import java.util.UUID; + +import com.movisens.smartgattlib.attributes.*; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.UuidObjectMap; + +public class Characteristics +{ + + public static final Characteristic DEFAULT = new Characteristic("0000", "Default Characteristic", DefaultAttribute.class); + public static final Characteristic LONGITUDE = new Characteristic("2aaf", "Longitude", DefaultAttribute.class); + public static final Characteristic MAGNETIC_FLUX_DENSITY_2D = new Characteristic("2aa0", "Magnetic Flux Density - 2D", DefaultAttribute.class); + public static final Characteristic MAGNETIC_FLUX_DENSITY_3D = new Characteristic("2aa1", "Magnetic Flux Density - 3D", DefaultAttribute.class); + public static final Characteristic AEROBIC_HEART_RATE_LOWER_LIMIT = new Characteristic("2a7e", "Aerobic Heart Rate Lower Limit", DefaultAttribute.class); + public static final Characteristic AEROBIC_HEART_RATE_UPPER_LIMIT = new Characteristic("2a84", "Aerobic Heart Rate Upper Limit", DefaultAttribute.class); + public static final Characteristic AEROBIC_THRESHOLD = new Characteristic("2a7f", "Aerobic Threshold", DefaultAttribute.class); + public static final Characteristic AGE = new Characteristic("2a80", "Age", Age.class); + public static final Characteristic AGGREGATE = new Characteristic("2a5a", "Aggregate", DefaultAttribute.class); + public static final Characteristic ALERT_CATEGORY_ID = new Characteristic("2a43", "Alert Category ID", DefaultAttribute.class); + public static final Characteristic ALERT_CATEGORY_ID_BIT_MASK = new Characteristic("2a42", "Alert Category ID Bit Mask", DefaultAttribute.class); + public static final Characteristic ALERT_LEVEL = new Characteristic("2a06", "Alert Level", DefaultAttribute.class); + public static final Characteristic ALERT_NOTIFICATION_CONTROL_POINT = new Characteristic("2a44", "Alert Notification Control Point", DefaultAttribute.class); + public static final Characteristic ALERT_STATUS = new Characteristic("2a3f", "Alert Status", DefaultAttribute.class); + public static final Characteristic ALTITUDE = new Characteristic("2ab3", "Altitude", DefaultAttribute.class); + public static final Characteristic ANAEROBIC_HEART_RATE_LOWER_LIMIT = new Characteristic("2a81", "Anaerobic Heart Rate Lower Limit", DefaultAttribute.class); + public static final Characteristic ANAEROBIC_HEART_RATE_UPPER_LIMIT = new Characteristic("2a82", "Anaerobic Heart Rate Upper Limit", DefaultAttribute.class); + public static final Characteristic ANAEROBIC_THRESHOLD = new Characteristic("2a83", "Anaerobic Threshold", DefaultAttribute.class); + public static final Characteristic ANALOG = new Characteristic("2a58", "Analog", DefaultAttribute.class); + public static final Characteristic ANALOG_OUTPUT = new Characteristic("2a59", "Analog Output", DefaultAttribute.class); + public static final Characteristic APPARENT_WIND_DIRECTION = new Characteristic("2a73", "Apparent Wind Direction", DefaultAttribute.class); + public static final Characteristic APPARENT_WIND_SPEED = new Characteristic("2a72", "Apparent Wind Speed", DefaultAttribute.class); + public static final Characteristic APPEARANCE = new Characteristic("2a01", "Appearance", Appearance.class); + public static final Characteristic BAROMETRIC_PRESSURE_TREND = new Characteristic("2aa3", "Barometric Pressure Trend", DefaultAttribute.class); + public static final Characteristic BATTERY_LEVEL = new Characteristic("2a19", "Battery Level", BatteryLevel.class); + public static final Characteristic BATTERY_LEVEL_STATE = new Characteristic("2a1b", "Battery Level State", DefaultAttribute.class); + public static final Characteristic BATTERY_POWER_STATE = new Characteristic("2a1a", "Battery Power State", DefaultAttribute.class); + public static final Characteristic BLOOD_PRESSURE_FEATURE = new Characteristic("2a49", "Blood Pressure Feature", DefaultAttribute.class); + public static final Characteristic BLOOD_PRESSURE_MEASUREMENT = new Characteristic("2a35", "Blood Pressure Measurement", DefaultAttribute.class); + public static final Characteristic BODY_COMPOSITION_FEATURE = new Characteristic("2a9b", "Body Composition Feature", DefaultAttribute.class); + public static final Characteristic BODY_COMPOSITION_MEASUREMENT = new Characteristic("2a9c", "Body Composition Measurement", DefaultAttribute.class); + public static final Characteristic BODY_SENSOR_LOCATION = new Characteristic("2a38", "Body Sensor Location", BodySensorLocation.class); + public static final Characteristic BOND_MANAGEMENT_CONTROL_POINT = new Characteristic("2aa4", "Bond Management Control Point", DefaultAttribute.class); + public static final Characteristic BOND_MANAGEMENT_FEATURE = new Characteristic("2aa5", "Bond Management Features", DefaultAttribute.class); + public static final Characteristic BOOT_KEYBOARD_INPUT_REPORT = new Characteristic("2a22", "Boot Keyboard Input Report", DefaultAttribute.class); + public static final Characteristic BOOT_KEYBOARD_OUTPUT_REPORT = new Characteristic("2a32", "Boot Keyboard Output Report", DefaultAttribute.class); + public static final Characteristic BOOT_MOUSE_INPUT_REPORT = new Characteristic("2a33", "Boot Mouse Input Report", DefaultAttribute.class); + public static final Characteristic CGM_FEATURE = new Characteristic("2aa8", "CGM Feature", DefaultAttribute.class); + public static final Characteristic CGM_MEASUREMENT = new Characteristic("2aa7", "CGM Measurement", DefaultAttribute.class); + public static final Characteristic CGM_SESSION_RUN_TIME = new Characteristic("2aab", "CGM Session Run Time", DefaultAttribute.class); + public static final Characteristic CGM_SESSION_START_TIME = new Characteristic("2aaa", "CGM Session Start Time", DefaultAttribute.class); + public static final Characteristic CGM_SPECIFIC_OPS_CONTROL_POINT = new Characteristic("2aac", "CGM Specific Ops Control Point", DefaultAttribute.class); + public static final Characteristic CGM_STATUS = new Characteristic("2aa9", "CGM Status", DefaultAttribute.class); + public static final Characteristic CROSS_TRAINER_DATA = new Characteristic("2ace", "Cross Trainer Data", DefaultAttribute.class); + public static final Characteristic CSC_FEATURE = new Characteristic("2a5c", "CSC Feature", DefaultAttribute.class); + public static final Characteristic CSC_MEASUREMENT = new Characteristic("2a5b", "CSC Measurement", CyclingSpeedCadenceMeasurement.class); + public static final Characteristic CURRENT_TIME = new Characteristic("2a2b", "Current Time", DefaultAttribute.class); + public static final Characteristic CYCLING_POWER_CONTROL_POINT = new Characteristic("2a66", "Cycling Power Control Point", DefaultAttribute.class); + public static final Characteristic CYCLING_POWER_FEATURE = new Characteristic("2a65", "Cycling Power Feature", DefaultAttribute.class); + public static final Characteristic CYCLING_POWER_MEASUREMENT = new Characteristic("2a63", "Cycling Power Measurement", DefaultAttribute.class); + public static final Characteristic CYCLING_POWER_VECTOR = new Characteristic("2a64", "Cycling Power Vector", DefaultAttribute.class); + public static final Characteristic DATABASE_CHANGE_INCREMENT = new Characteristic("2a99", "Database Change Increment", DefaultAttribute.class); + public static final Characteristic DATE_OF_BIRTH = new Characteristic("2a85", "Date of Birth", DateOfBirth.class); + public static final Characteristic DATE_OF_THRESHOLD_ASSESSMENT = new Characteristic("2a86", "Date of Threshold Assessment", DefaultAttribute.class); + public static final Characteristic DATE_TIME = new Characteristic("2a08", "Date Time", DefaultAttribute.class); + public static final Characteristic DAY_DATE_TIME = new Characteristic("2a0a", "Day Date Time", DefaultAttribute.class); + public static final Characteristic DAY_OF_WEEK = new Characteristic("2a09", "Day of Week", DefaultAttribute.class); + public static final Characteristic DESCRIPTOR_VALUE_CHANGED = new Characteristic("2a7d", "Descriptor Value Changed", DefaultAttribute.class); + public static final Characteristic DEVICE_NAME = new Characteristic("2a00", "Device Name", DeviceName.class); + public static final Characteristic DEW_POINT = new Characteristic("2a7b", "Dew Point", DefaultAttribute.class); + public static final Characteristic DIGITAL = new Characteristic("2a56", "Digital", DefaultAttribute.class); + public static final Characteristic DIGITAL_OUTPUT = new Characteristic("2a57", "Digital Output", DefaultAttribute.class); + public static final Characteristic DST_OFFSET = new Characteristic("2a0d", "DST Offset", DefaultAttribute.class); + public static final Characteristic ELEVATION = new Characteristic("2a6c", "Elevation", DefaultAttribute.class); + public static final Characteristic EMAIL_ADDRESS = new Characteristic("2a87", "Email Address", DefaultAttribute.class); + public static final Characteristic EXACT_TIME_100 = new Characteristic("2a0b", "Exact Time 100", DefaultAttribute.class); + public static final Characteristic EXACT_TIME_256 = new Characteristic("2a0c", "Exact Time 256", DefaultAttribute.class); + public static final Characteristic FAT_BURN_HEART_RATE_LOWER_LIMIT = new Characteristic("2a88", "Fat Burn Heart Rate Lower Limit", DefaultAttribute.class); + public static final Characteristic FAT_BURN_HEART_RATE_UPPER_LIMIT = new Characteristic("2a89", "Fat Burn Heart Rate Upper Limit", DefaultAttribute.class); + public static final Characteristic FIRMWARE_REVISION_STRING = new Characteristic("2a26", "Firmware Revision String", FirmwareRevisionString.class); + public static final Characteristic FIRST_NAME = new Characteristic("2a8a", "First Name", DefaultAttribute.class); + public static final Characteristic FITNESS_MACHINE_CONTROL_POINT = new Characteristic("2ad9", "Fitness Machine Control Point", DefaultAttribute.class); + public static final Characteristic FITNESS_MACHINE_FEATURE = new Characteristic("2acc", "Fitness Machine Feature", DefaultAttribute.class); + public static final Characteristic FITNESS_MACHINE_STATUS = new Characteristic("2ada", "Fitness Machine Status", DefaultAttribute.class); + public static final Characteristic FIVE_ZONE_HEART_RATE_LIMITS = new Characteristic("2a8b", "Five Zone Heart Rate Limits", DefaultAttribute.class); + public static final Characteristic FLOOR_NUMBER = new Characteristic("2ab2", "Floor Number", DefaultAttribute.class); + public static final Characteristic GENDER = new Characteristic("2a8c", "Gender", Gender.class); + public static final Characteristic GLUCOSE_FEATURE = new Characteristic("2a51", "Glucose Feature", DefaultAttribute.class); + public static final Characteristic GLUCOSE_MEASUREMENT = new Characteristic("2a18", "Glucose Measurement", DefaultAttribute.class); + public static final Characteristic GLUCOSE_MEASUREMENT_CONTEXT = new Characteristic("2a34", "Glucose Measurement Context", DefaultAttribute.class); + public static final Characteristic GUST_FACTOR = new Characteristic("2a74", "Gust Factor", DefaultAttribute.class); + public static final Characteristic HARDWARE_REVISION_STRING = new Characteristic("2a27", "Hardware Revision String", DefaultAttribute.class); + public static final Characteristic HEART_RATE_CONTROL_POINT = new Characteristic("2a39", "Heart Rate Control Point", DefaultAttribute.class); + public static final Characteristic HEART_RATE_MAX = new Characteristic("2a8d", "Heart Rate Max", DefaultAttribute.class); + public static final Characteristic HEART_RATE_MEASUREMENT = new Characteristic("2a37", "Heart Rate Measurement", HeartRateMeasurement.class); + public static final Characteristic HEAT_INDEX = new Characteristic("2a7a", "Heat Index", DefaultAttribute.class); + public static final Characteristic HEIGHT = new Characteristic("2a8e", "Height", Height.class); + public static final Characteristic HID_CONTROL_POINT = new Characteristic("2a4c", "HID Control Point", DefaultAttribute.class); + public static final Characteristic HID_INFORMATION = new Characteristic("2a4a", "HID Information", DefaultAttribute.class); + public static final Characteristic HIP_CIRCUMFERENCE = new Characteristic("2a8f", "Hip Circumference", DefaultAttribute.class); + public static final Characteristic HTTP_CONTROL_POINT = new Characteristic("2aba", "HTTP Control Point", DefaultAttribute.class); + public static final Characteristic HTTP_ENTITY_BODY = new Characteristic("2ab9", "HTTP Entity Body", DefaultAttribute.class); + public static final Characteristic HTTP_HEADERS = new Characteristic("2ab7", "HTTP Headers", DefaultAttribute.class); + public static final Characteristic HTTP_STATUS_CODE = new Characteristic("2ab8", "HTTP Status Code", DefaultAttribute.class); + public static final Characteristic HTTPS_SECURITY = new Characteristic("2abb", "HTTPS Security", DefaultAttribute.class); + public static final Characteristic HUMIDITY = new Characteristic("2a6f", "Humidity", DefaultAttribute.class); + public static final Characteristic IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST = new Characteristic("2a2a", "IEEE 11073-20601 Regulatory Certification Data List", DefaultAttribute.class); + public static final Characteristic INDOOR_BIKE_DATA = new Characteristic("2ad2", "Indoor Bike Data", DefaultAttribute.class); + public static final Characteristic INDOOR_POSITIONING_CONFIGURATION = new Characteristic("2aad", "Indoor Positioning Configuration", DefaultAttribute.class); + public static final Characteristic INTERMEDIATE_CUFF_PRESSURE = new Characteristic("2a36", "Intermediate Cuff Pressure", DefaultAttribute.class); + public static final Characteristic INTERMEDIATE_TEMPERATURE = new Characteristic("2a1e", "Intermediate Temperature", DefaultAttribute.class); + public static final Characteristic IRRADIANCE = new Characteristic("2a77", "Irradiance", DefaultAttribute.class); + public static final Characteristic LANGUAGE = new Characteristic("2aa2", "Language", DefaultAttribute.class); + public static final Characteristic LAST_NAME = new Characteristic("2a90", "Last Name", DefaultAttribute.class); + public static final Characteristic LATITUDE = new Characteristic("2aae", "Latitude", DefaultAttribute.class); + public static final Characteristic LN_CONTROL_POINT = new Characteristic("2a6b", "LN Control Point", DefaultAttribute.class); + public static final Characteristic LN_FEATURE = new Characteristic("2a6a", "LN Feature", DefaultAttribute.class); + public static final Characteristic LOCAL_EAST_COORDINATE = new Characteristic("2ab1", "Local East Coordinate", DefaultAttribute.class); + public static final Characteristic LOCAL_NORTH_COORDINATE = new Characteristic("2ab0", "Local North Coordinate", DefaultAttribute.class); + public static final Characteristic LOCAL_TIME_INFORMATION = new Characteristic("2a0f", "Local Time Information", DefaultAttribute.class); + public static final Characteristic LOCATION_AND_SPEED = new Characteristic("2a67", "Location and Speed Characteristic", DefaultAttribute.class); + public static final Characteristic LOCATION_NAME = new Characteristic("2ab5", "Location Name", DefaultAttribute.class); + public static final Characteristic MAGNETIC_DECLINATION = new Characteristic("2a2c", "Magnetic Declination", DefaultAttribute.class); + public static final Characteristic MANUFACTURER_NAME_STRING = new Characteristic("2a29", "Manufacturer Name String", ManufacturerNameString.class); + public static final Characteristic MAXIMUM_RECOMMENDED_HEART_RATE = new Characteristic("2a91", "Maximum Recommended Heart Rate", DefaultAttribute.class); + public static final Characteristic MEASUREMENT_INTERVAL = new Characteristic("2a21", "Measurement Interval", DefaultAttribute.class); + public static final Characteristic MODEL_NUMBER_STRING = new Characteristic("2a24", "Model Number String", ModelNumberString.class); + public static final Characteristic NAVIGATION = new Characteristic("2a68", "Navigation", DefaultAttribute.class); + public static final Characteristic NETWORK_AVAILABILITY = new Characteristic("2a3e", "Network Availability", DefaultAttribute.class); + public static final Characteristic NEW_ALERT = new Characteristic("2a46", "New Alert", DefaultAttribute.class); + public static final Characteristic OBJECT_ACTION_CONTROL_POINT = new Characteristic("2ac5", "Object Action Control Point", DefaultAttribute.class); + public static final Characteristic OBJECT_CHANGED = new Characteristic("2ac8", "Object Changed", DefaultAttribute.class); + public static final Characteristic OBJECT_FIRST_CREATED = new Characteristic("2ac1", "Object First-Created", DefaultAttribute.class); + public static final Characteristic OBJECT_ID = new Characteristic("2ac3", "Object ID", DefaultAttribute.class); + public static final Characteristic OBJECT_LAST_MODIFIED = new Characteristic("2ac2", "Object Last-Modified", DefaultAttribute.class); + public static final Characteristic OBJECT_LIST_CONTROL_POINT = new Characteristic("2ac6", "Object List Control Point", DefaultAttribute.class); + public static final Characteristic OBJECT_LIST_FILTER = new Characteristic("2ac7", "Object List Filter", DefaultAttribute.class); + public static final Characteristic OBJECT_NAME = new Characteristic("2abe", "Object Name", DefaultAttribute.class); + public static final Characteristic OBJECT_PROPERTIES = new Characteristic("2ac4", "Object Properties", DefaultAttribute.class); + public static final Characteristic OBJECT_SIZE = new Characteristic("2ac0", "Object Size", DefaultAttribute.class); + public static final Characteristic OBJECT_TYPE = new Characteristic("2abf", "Object Type", DefaultAttribute.class); + public static final Characteristic OTS_FEATURE = new Characteristic("2abd", "OTS Feature", DefaultAttribute.class); + public static final Characteristic PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS = new Characteristic("2a04", "Peripheral Preferred Connection Parameters", DefaultAttribute.class); + public static final Characteristic PERIPHERAL_PRIVACY_FLAG = new Characteristic("2a02", "Peripheral Privacy Flag", DefaultAttribute.class); + public static final Characteristic PLX_CONTINUOUS_MEASUREMENT = new Characteristic("2a5f", "PLX Continuous Measurement Characteristic", DefaultAttribute.class); + public static final Characteristic PLX_FEATURES = new Characteristic("2a60", "PLX Features", DefaultAttribute.class); + public static final Characteristic PLX_SPOT_CHECK_MEASUREMENT = new Characteristic("2a5e", "PLX Spot-Check Measurement", DefaultAttribute.class); + public static final Characteristic PNP_ID = new Characteristic("2a50", "PnP ID", DefaultAttribute.class); + public static final Characteristic POLLEN_CONCENTRATION = new Characteristic("2a75", "Pollen Concentration", DefaultAttribute.class); + public static final Characteristic POSITION_2D = new Characteristic("2a2f", "Position 2D", DefaultAttribute.class); + public static final Characteristic POSITION_3D = new Characteristic("2a30", "Position 3D", DefaultAttribute.class); + public static final Characteristic POSITION_QUALITY = new Characteristic("2a69", "Position Quality", DefaultAttribute.class); + public static final Characteristic PRESSURE = new Characteristic("2a6d", "Pressure", DefaultAttribute.class); + public static final Characteristic PROTOCOL_MODE = new Characteristic("2a4e", "Protocol Mode", DefaultAttribute.class); + public static final Characteristic PULSE_OXIMETRY_CONTROL_POINT = new Characteristic("2a62", "Pulse Oximetry Control Point", DefaultAttribute.class); + public static final Characteristic PULSE_OXIMETRY_PULSATILE_EVENT = new Characteristic("2a60", "Pulse Oximetry Pulsatile Event Characteristic", DefaultAttribute.class); + public static final Characteristic RAINFALL = new Characteristic("2a78", "Rainfall", DefaultAttribute.class); + public static final Characteristic RECONNECTION_ADDRESS = new Characteristic("2a03", "Reconnection Address", DefaultAttribute.class); + public static final Characteristic RECORD_ACCESS_CONTROL_POINT = new Characteristic("2a52", "Record Access Control Point", DefaultAttribute.class); + public static final Characteristic REFERENCE_TIME_INFORMATION = new Characteristic("2a14", "Reference Time Information", DefaultAttribute.class); + public static final Characteristic REMOVABLE = new Characteristic("2a3a", "Removable", DefaultAttribute.class); + public static final Characteristic REPORT = new Characteristic("2a4d", "Report", DefaultAttribute.class); + public static final Characteristic REPORT_MAP = new Characteristic("2a4b", "Report Map", DefaultAttribute.class); + public static final Characteristic RESOLVABLE_PRIVATE_ADDRESS_ONLY = new Characteristic("2ac9", "Resolvable Private Address Only", DefaultAttribute.class); + public static final Characteristic RESTING_HEART_RATE = new Characteristic("2a92", "Resting Heart Rate", DefaultAttribute.class); + public static final Characteristic RINGER_CONTROL_POINT = new Characteristic("2a40", "Ringer Control point", DefaultAttribute.class); + public static final Characteristic RINGER_SETTING = new Characteristic("2a41", "Ringer Setting", DefaultAttribute.class); + public static final Characteristic ROWER_DATA = new Characteristic("2ad1", "Rower Data", DefaultAttribute.class); + public static final Characteristic RSC_FEATURE = new Characteristic("2a54", "RSC Feature", DefaultAttribute.class); + public static final Characteristic RSC_MEASUREMENT = new Characteristic("2a53", "RSC Measurement", DefaultAttribute.class); + public static final Characteristic SC_CONTROL_POINT = new Characteristic("2a55", "SC Control Point", DefaultAttribute.class); + public static final Characteristic SCAN_INTERVAL_WINDOW = new Characteristic("2a4f", "Scan Interval Window", DefaultAttribute.class); + public static final Characteristic SCAN_REFRESH = new Characteristic("2a31", "Scan Refresh", DefaultAttribute.class); + public static final Characteristic SCIENTIFIC_TEMPERATURE_CELSIUS = new Characteristic("2a3c", "Scientific Temperature Celsius", DefaultAttribute.class); + public static final Characteristic SECONDARY_TIME_ZONE = new Characteristic("2a10", "Secondary Time Zone", DefaultAttribute.class); + public static final Characteristic SENSOR_LOCATION = new Characteristic("2a5d", "Sensor Location", DefaultAttribute.class); + public static final Characteristic SERIAL_NUMBER_STRING = new Characteristic("2a25", "Serial Number String", SerialNumberString.class); + public static final Characteristic SERVICE_CHANGED = new Characteristic("2a05", "Service Changed", DefaultAttribute.class); + public static final Characteristic SERVICE_REQUIRED = new Characteristic("2a3b", "Service Required", DefaultAttribute.class); + public static final Characteristic SOFTWARE_REVISION_STRING = new Characteristic("2a28", "Software Revision String", DefaultAttribute.class); + public static final Characteristic SPORT_TYPE_FOR_AEROBIC_AND_ANAEROBIC_THRESHOLDS = new Characteristic("2a93", "Sport Type for Aerobic and Anaerobic Thresholds", DefaultAttribute.class); + public static final Characteristic STAIR_CLIMBER_DATA = new Characteristic("2ad0", "Stair Climber Data", DefaultAttribute.class); + public static final Characteristic STEP_CLIMBER_DATA = new Characteristic("2acf", "Step Climber Data", DefaultAttribute.class); + public static final Characteristic STRING_ = new Characteristic("2a3d", "String", DefaultAttribute.class); + public static final Characteristic SUPPORTED_HEART_RATE_RANGE = new Characteristic("2ad7", "Supported Heart Rate Range", DefaultAttribute.class); + public static final Characteristic SUPPORTED_INCLINATION_RANGE = new Characteristic("2ad5", "Supported Inclination Range", DefaultAttribute.class); + public static final Characteristic SUPPORTED_NEW_ALERT_CATEGORY = new Characteristic("2a47", "Supported New Alert Category", DefaultAttribute.class); + public static final Characteristic SUPPORTED_POWER_RANGE = new Characteristic("2ad8", "Supported Power Range", DefaultAttribute.class); + public static final Characteristic SUPPORTED_RESISTANCE_LEVEL_RANGE = new Characteristic("2ad6", "Supported Resistance Level Range", DefaultAttribute.class); + public static final Characteristic SUPPORTED_SPEED_RANGE = new Characteristic("2ad4", "Supported Speed Range", DefaultAttribute.class); + public static final Characteristic SUPPORTED_UNREAD_ALERT_CATEGORY = new Characteristic("2a48", "Supported Unread Alert Category", DefaultAttribute.class); + public static final Characteristic SYSTEM_ID = new Characteristic("2a23", "System ID", DefaultAttribute.class); + public static final Characteristic TDS_CONTROL_POINT = new Characteristic("2abc", "TDS Control Point", DefaultAttribute.class); + public static final Characteristic TEMPERATURE = new Characteristic("2a6e", "Temperature", DefaultAttribute.class); + public static final Characteristic TEMPERATURE_CELSIUS = new Characteristic("2a1f", "Temperature Celsius", DefaultAttribute.class); + public static final Characteristic TEMPERATURE_FAHRENHEIT = new Characteristic("2a20", "Temperature Fahrenheit", DefaultAttribute.class); + public static final Characteristic TEMPERATURE_MEASUREMENT = new Characteristic("2a1c", "Temperature Measurement", DefaultAttribute.class); + public static final Characteristic TEMPERATURE_TYPE = new Characteristic("2a1d", "Temperature Type", DefaultAttribute.class); + public static final Characteristic THREE_ZONE_HEART_RATE_LIMITS = new Characteristic("2a94", "Three Zone Heart Rate Limits", DefaultAttribute.class); + public static final Characteristic TIME_ACCURACY = new Characteristic("2a12", "Time Accuracy", DefaultAttribute.class); + public static final Characteristic TIME_BROADCAST = new Characteristic("2a15", "Time Broadcast", DefaultAttribute.class); + public static final Characteristic TIME_SOURCE = new Characteristic("2a13", "Time Source", DefaultAttribute.class); + public static final Characteristic TIME_UPDATE_CONTROL_POINT = new Characteristic("2a16", "Time Update Control Point", DefaultAttribute.class); + public static final Characteristic TIME_UPDATE_STATE = new Characteristic("2a17", "Time Update State", DefaultAttribute.class); + public static final Characteristic TIME_WITH_DST = new Characteristic("2a11", "Time with DST", DefaultAttribute.class); + public static final Characteristic TIME_ZONE = new Characteristic("2a0e", "Time Zone", DefaultAttribute.class); + public static final Characteristic TRAINING_STATUS = new Characteristic("2ad3", "Training Status", DefaultAttribute.class); + public static final Characteristic TREADMILL_DATA = new Characteristic("2acd", "Treadmill Data", DefaultAttribute.class); + public static final Characteristic TRUE_WIND_DIRECTION = new Characteristic("2a71", "True Wind Direction", DefaultAttribute.class); + public static final Characteristic TRUE_WIND_SPEED = new Characteristic("2a70", "True Wind Speed", DefaultAttribute.class); + public static final Characteristic TWO_ZONE_HEART_RATE_LIMIT = new Characteristic("2a95", "Two Zone Heart Rate Limit", DefaultAttribute.class); + public static final Characteristic TX_POWER_LEVEL = new Characteristic("2a07", "Tx Power Level", DefaultAttribute.class); + public static final Characteristic UNCERTAINTY = new Characteristic("2ab4", "Uncertainty", DefaultAttribute.class); + public static final Characteristic UNREAD_ALERT_STATUS = new Characteristic("2a45", "Unread Alert Status", DefaultAttribute.class); + public static final Characteristic URI = new Characteristic("2ab6", "URI", DefaultAttribute.class); + public static final Characteristic USER_CONTROL_POINT = new Characteristic("2a9f", "User Control Point", DefaultAttribute.class); + public static final Characteristic USER_INDEX = new Characteristic("2a9a", "User Index", DefaultAttribute.class); + public static final Characteristic UV_INDEX = new Characteristic("2a76", "UV Index", DefaultAttribute.class); + public static final Characteristic VO2_MAX = new Characteristic("2a96", "VO2 Max", DefaultAttribute.class); + public static final Characteristic WAIST_CIRCUMFERENCE = new Characteristic("2a97", "Waist Circumference", DefaultAttribute.class); + public static final Characteristic WEIGHT = new Characteristic("2a98", "Weight", Weight.class); + public static final Characteristic WEIGHT_MEASUREMENT = new Characteristic("2a9d", "Weight Measurement", DefaultAttribute.class); + public static final Characteristic WEIGHT_SCALE_FEATURE = new Characteristic("2a9e", "Weight Scale Feature", DefaultAttribute.class); + public static final Characteristic WIND_CHILL = new Characteristic("2a79", "Wind Chill", DefaultAttribute.class); + + private static UuidObjectMap characteristics = new UuidObjectMap(); + + static + { + characteristics.put(LONGITUDE); + characteristics.put(MAGNETIC_FLUX_DENSITY_2D); + characteristics.put(MAGNETIC_FLUX_DENSITY_3D); + characteristics.put(AEROBIC_HEART_RATE_LOWER_LIMIT); + characteristics.put(AEROBIC_HEART_RATE_UPPER_LIMIT); + characteristics.put(AEROBIC_THRESHOLD); + characteristics.put(AGE); + characteristics.put(AGGREGATE); + characteristics.put(ALERT_CATEGORY_ID); + characteristics.put(ALERT_CATEGORY_ID_BIT_MASK); + characteristics.put(ALERT_LEVEL); + characteristics.put(ALERT_NOTIFICATION_CONTROL_POINT); + characteristics.put(ALERT_STATUS); + characteristics.put(ALTITUDE); + characteristics.put(ANAEROBIC_HEART_RATE_LOWER_LIMIT); + characteristics.put(ANAEROBIC_HEART_RATE_UPPER_LIMIT); + characteristics.put(ANAEROBIC_THRESHOLD); + characteristics.put(ANALOG); + characteristics.put(ANALOG_OUTPUT); + characteristics.put(APPARENT_WIND_DIRECTION); + characteristics.put(APPARENT_WIND_SPEED); + characteristics.put(APPEARANCE); + characteristics.put(BAROMETRIC_PRESSURE_TREND); + characteristics.put(BATTERY_LEVEL); + characteristics.put(BATTERY_LEVEL_STATE); + characteristics.put(BATTERY_POWER_STATE); + characteristics.put(BLOOD_PRESSURE_FEATURE); + characteristics.put(BLOOD_PRESSURE_MEASUREMENT); + characteristics.put(BODY_COMPOSITION_FEATURE); + characteristics.put(BODY_COMPOSITION_MEASUREMENT); + characteristics.put(BODY_SENSOR_LOCATION); + characteristics.put(BOND_MANAGEMENT_CONTROL_POINT); + characteristics.put(BOND_MANAGEMENT_FEATURE); + characteristics.put(BOOT_KEYBOARD_INPUT_REPORT); + characteristics.put(BOOT_KEYBOARD_OUTPUT_REPORT); + characteristics.put(BOOT_MOUSE_INPUT_REPORT); + characteristics.put(CGM_FEATURE); + characteristics.put(CGM_MEASUREMENT); + characteristics.put(CGM_SESSION_RUN_TIME); + characteristics.put(CGM_SESSION_START_TIME); + characteristics.put(CGM_SPECIFIC_OPS_CONTROL_POINT); + characteristics.put(CGM_STATUS); + characteristics.put(CROSS_TRAINER_DATA); + characteristics.put(CSC_FEATURE); + characteristics.put(CSC_MEASUREMENT); + characteristics.put(CURRENT_TIME); + characteristics.put(CYCLING_POWER_CONTROL_POINT); + characteristics.put(CYCLING_POWER_FEATURE); + characteristics.put(CYCLING_POWER_MEASUREMENT); + characteristics.put(CYCLING_POWER_VECTOR); + characteristics.put(DATABASE_CHANGE_INCREMENT); + characteristics.put(DATE_OF_BIRTH); + characteristics.put(DATE_OF_THRESHOLD_ASSESSMENT); + characteristics.put(DATE_TIME); + characteristics.put(DAY_DATE_TIME); + characteristics.put(DAY_OF_WEEK); + characteristics.put(DESCRIPTOR_VALUE_CHANGED); + characteristics.put(DEVICE_NAME); + characteristics.put(DEW_POINT); + characteristics.put(DIGITAL); + characteristics.put(DIGITAL_OUTPUT); + characteristics.put(DST_OFFSET); + characteristics.put(ELEVATION); + characteristics.put(EMAIL_ADDRESS); + characteristics.put(EXACT_TIME_100); + characteristics.put(EXACT_TIME_256); + characteristics.put(FAT_BURN_HEART_RATE_LOWER_LIMIT); + characteristics.put(FAT_BURN_HEART_RATE_UPPER_LIMIT); + characteristics.put(FIRMWARE_REVISION_STRING); + characteristics.put(FIRST_NAME); + characteristics.put(FITNESS_MACHINE_CONTROL_POINT); + characteristics.put(FITNESS_MACHINE_FEATURE); + characteristics.put(FITNESS_MACHINE_STATUS); + characteristics.put(FIVE_ZONE_HEART_RATE_LIMITS); + characteristics.put(FLOOR_NUMBER); + characteristics.put(GENDER); + characteristics.put(GLUCOSE_FEATURE); + characteristics.put(GLUCOSE_MEASUREMENT); + characteristics.put(GLUCOSE_MEASUREMENT_CONTEXT); + characteristics.put(GUST_FACTOR); + characteristics.put(HARDWARE_REVISION_STRING); + characteristics.put(HEART_RATE_CONTROL_POINT); + characteristics.put(HEART_RATE_MAX); + characteristics.put(HEART_RATE_MEASUREMENT); + characteristics.put(HEAT_INDEX); + characteristics.put(HEIGHT); + characteristics.put(HID_CONTROL_POINT); + characteristics.put(HID_INFORMATION); + characteristics.put(HIP_CIRCUMFERENCE); + characteristics.put(HTTP_CONTROL_POINT); + characteristics.put(HTTP_ENTITY_BODY); + characteristics.put(HTTP_HEADERS); + characteristics.put(HTTP_STATUS_CODE); + characteristics.put(HTTPS_SECURITY); + characteristics.put(HUMIDITY); + characteristics.put(IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST); + characteristics.put(INDOOR_BIKE_DATA); + characteristics.put(INDOOR_POSITIONING_CONFIGURATION); + characteristics.put(INTERMEDIATE_CUFF_PRESSURE); + characteristics.put(INTERMEDIATE_TEMPERATURE); + characteristics.put(IRRADIANCE); + characteristics.put(LANGUAGE); + characteristics.put(LAST_NAME); + characteristics.put(LATITUDE); + characteristics.put(LN_CONTROL_POINT); + characteristics.put(LN_FEATURE); + characteristics.put(LOCAL_EAST_COORDINATE); + characteristics.put(LOCAL_NORTH_COORDINATE); + characteristics.put(LOCAL_TIME_INFORMATION); + characteristics.put(LOCATION_AND_SPEED); + characteristics.put(LOCATION_NAME); + characteristics.put(MAGNETIC_DECLINATION); + characteristics.put(MANUFACTURER_NAME_STRING); + characteristics.put(MAXIMUM_RECOMMENDED_HEART_RATE); + characteristics.put(MEASUREMENT_INTERVAL); + characteristics.put(MODEL_NUMBER_STRING); + characteristics.put(NAVIGATION); + characteristics.put(NETWORK_AVAILABILITY); + characteristics.put(NEW_ALERT); + characteristics.put(OBJECT_ACTION_CONTROL_POINT); + characteristics.put(OBJECT_CHANGED); + characteristics.put(OBJECT_FIRST_CREATED); + characteristics.put(OBJECT_ID); + characteristics.put(OBJECT_LAST_MODIFIED); + characteristics.put(OBJECT_LIST_CONTROL_POINT); + characteristics.put(OBJECT_LIST_FILTER); + characteristics.put(OBJECT_NAME); + characteristics.put(OBJECT_PROPERTIES); + characteristics.put(OBJECT_SIZE); + characteristics.put(OBJECT_TYPE); + characteristics.put(OTS_FEATURE); + characteristics.put(PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS); + characteristics.put(PERIPHERAL_PRIVACY_FLAG); + characteristics.put(PLX_CONTINUOUS_MEASUREMENT); + characteristics.put(PLX_FEATURES); + characteristics.put(PLX_SPOT_CHECK_MEASUREMENT); + characteristics.put(PNP_ID); + characteristics.put(POLLEN_CONCENTRATION); + characteristics.put(POSITION_2D); + characteristics.put(POSITION_3D); + characteristics.put(POSITION_QUALITY); + characteristics.put(PRESSURE); + characteristics.put(PROTOCOL_MODE); + characteristics.put(PULSE_OXIMETRY_CONTROL_POINT); + characteristics.put(PULSE_OXIMETRY_PULSATILE_EVENT); + characteristics.put(RAINFALL); + characteristics.put(RECONNECTION_ADDRESS); + characteristics.put(RECORD_ACCESS_CONTROL_POINT); + characteristics.put(REFERENCE_TIME_INFORMATION); + characteristics.put(REMOVABLE); + characteristics.put(REPORT); + characteristics.put(REPORT_MAP); + characteristics.put(RESOLVABLE_PRIVATE_ADDRESS_ONLY); + characteristics.put(RESTING_HEART_RATE); + characteristics.put(RINGER_CONTROL_POINT); + characteristics.put(RINGER_SETTING); + characteristics.put(ROWER_DATA); + characteristics.put(RSC_FEATURE); + characteristics.put(RSC_MEASUREMENT); + characteristics.put(SC_CONTROL_POINT); + characteristics.put(SCAN_INTERVAL_WINDOW); + characteristics.put(SCAN_REFRESH); + characteristics.put(SCIENTIFIC_TEMPERATURE_CELSIUS); + characteristics.put(SECONDARY_TIME_ZONE); + characteristics.put(SENSOR_LOCATION); + characteristics.put(SERIAL_NUMBER_STRING); + characteristics.put(SERVICE_CHANGED); + characteristics.put(SERVICE_REQUIRED); + characteristics.put(SOFTWARE_REVISION_STRING); + characteristics.put(SPORT_TYPE_FOR_AEROBIC_AND_ANAEROBIC_THRESHOLDS); + characteristics.put(STAIR_CLIMBER_DATA); + characteristics.put(STEP_CLIMBER_DATA); + characteristics.put(STRING_); + characteristics.put(SUPPORTED_HEART_RATE_RANGE); + characteristics.put(SUPPORTED_INCLINATION_RANGE); + characteristics.put(SUPPORTED_NEW_ALERT_CATEGORY); + characteristics.put(SUPPORTED_POWER_RANGE); + characteristics.put(SUPPORTED_RESISTANCE_LEVEL_RANGE); + characteristics.put(SUPPORTED_SPEED_RANGE); + characteristics.put(SUPPORTED_UNREAD_ALERT_CATEGORY); + characteristics.put(SYSTEM_ID); + characteristics.put(TDS_CONTROL_POINT); + characteristics.put(TEMPERATURE); + characteristics.put(TEMPERATURE_CELSIUS); + characteristics.put(TEMPERATURE_FAHRENHEIT); + characteristics.put(TEMPERATURE_MEASUREMENT); + characteristics.put(TEMPERATURE_TYPE); + characteristics.put(THREE_ZONE_HEART_RATE_LIMITS); + characteristics.put(TIME_ACCURACY); + characteristics.put(TIME_BROADCAST); + characteristics.put(TIME_SOURCE); + characteristics.put(TIME_UPDATE_CONTROL_POINT); + characteristics.put(TIME_UPDATE_STATE); + characteristics.put(TIME_WITH_DST); + characteristics.put(TIME_ZONE); + characteristics.put(TRAINING_STATUS); + characteristics.put(TREADMILL_DATA); + characteristics.put(TRUE_WIND_DIRECTION); + characteristics.put(TRUE_WIND_SPEED); + characteristics.put(TWO_ZONE_HEART_RATE_LIMIT); + characteristics.put(TX_POWER_LEVEL); + characteristics.put(UNCERTAINTY); + characteristics.put(UNREAD_ALERT_STATUS); + characteristics.put(URI); + characteristics.put(USER_CONTROL_POINT); + characteristics.put(USER_INDEX); + characteristics.put(UV_INDEX); + characteristics.put(VO2_MAX); + characteristics.put(WAIST_CIRCUMFERENCE); + characteristics.put(WEIGHT); + characteristics.put(WEIGHT_MEASUREMENT); + characteristics.put(WEIGHT_SCALE_FEATURE); + characteristics.put(WIND_CHILL); + } + + /** + * Looks up the characteristic for the given UUID. If the UUID is not found, a lookup in the smartgattlib is performed. + * + * @param uuid the UUID to look for + * @return the corresponding characteristic or the DEFAULT characteristic of smartgattlib if UUID is not found + */ + public static Characteristic lookup(UUID uuid) + { + Characteristic result = characteristics.get(uuid); + return result == null ? DEFAULT : result; + } + +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/Services.java b/src-gen/main/java/com/movisens/smartgattlib/Services.java new file mode 100644 index 0000000..5684d4e --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/Services.java @@ -0,0 +1,106 @@ +package com.movisens.smartgattlib; + +import java.util.UUID; + +import com.movisens.smartgattlib.helper.Service; +import com.movisens.smartgattlib.helper.UuidObjectMap; + +public class Services +{ + + public static final Service DEFAULT = new Service("0000", "Default Service"); + public static final Service ALERT_NOTIFICATION = new Service("1811", "Alert Notification Service"); + public static final Service AUTOMATION_IO = new Service("1815", "Automation IO"); + public static final Service BATTERY_SERVICE = new Service("180f", "Battery Service"); + public static final Service BLOOD_PRESSURE = new Service("1810", "Blood Pressure"); + public static final Service BODY_COMPOSITION = new Service("181b", "Body Composition"); + public static final Service BOND_MANAGEMENT = new Service("181e", "Bond Management Service"); + public static final Service CONTINUOUS_GLUCOSE_MONITORING = new Service("181f", "Continuous Glucose Monitoring"); + public static final Service CURRENT_TIME = new Service("1805", "Current Time Service"); + public static final Service CYCLING_POWER = new Service("1818", "Cycling Power"); + public static final Service CYCLING_SPEED_AND_CADENCE = new Service("1816", "Cycling Speed and Cadence"); + public static final Service DEVICE_INFORMATION = new Service("180a", "Device Information"); + public static final Service ENVIRONMENTAL_SENSING = new Service("181a", "Environmental Sensing"); + public static final Service FITNESS_MACHINE = new Service("1826", "Fitness Machine"); + public static final Service GENERIC_ACCESS = new Service("1800", " Generic Access"); + public static final Service GENERIC_ATTRIBUTE = new Service("1801", "Generic Attribute"); + public static final Service GLUCOSE = new Service("1808", "Glucose"); + public static final Service HEALTH_THERMOMETER = new Service("1809", "Health Thermometer"); + public static final Service HEART_RATE = new Service("180d", "Heart Rate"); + public static final Service HTTP_PROXY = new Service("1823", "HTTP Proxy"); + public static final Service HUMAN_INTERFACE_DEVICE = new Service("1812", "Human Interface Device"); + public static final Service IMMEDIATE_ALERT = new Service("1802", "Immediate Alert"); + public static final Service INDOOR_POSITIONING = new Service("1821", "Indoor Positioning"); + public static final Service INTERNET_PROTOCOL_SUPPORT = new Service("1820", "Internet Protocol Support Service"); + public static final Service LINK_LOSS = new Service("1803", "Link Loss"); + public static final Service LOCATION_AND_NAVIGATION = new Service("1819", "Location and Navigation"); + public static final Service MESH_PROVISIONING = new Service("1827", "Mesh Provisioning Service"); + public static final Service MESH_PROXY = new Service("1828", "Mesh Proxy Service"); + public static final Service NEXT_DST_CHANGE = new Service("1807", "Next DST Change Service"); + public static final Service OBJECT_TRANSFER = new Service("1825", "Object Transfer Service"); + public static final Service PHONE_ALERT_STATUS = new Service("180e", "Phone Alert Status Service"); + public static final Service PULSE_OXIMETER = new Service("1822", "Pulse Oximeter Service"); + public static final Service REFERENCE_TIME_UPDATE = new Service("1806", "Reference Time Update Service"); + public static final Service RUNNING_SPEED_AND_CADENCE = new Service("1814", "Running Speed and Cadence"); + public static final Service SCAN_PARAMETERS = new Service("1813", "Scan Parameters"); + public static final Service TRANSPORT_DISCOVERY = new Service("1824", "Transport Discovery"); + public static final Service TX_POWER = new Service("1804", "Tx Power"); + public static final Service USER_DATA = new Service("181c", "User Data"); + public static final Service WEIGHT_SCALE = new Service("181d", "Weight Scale"); + + private static UuidObjectMap services = new UuidObjectMap(); + + static + { + services.put(ALERT_NOTIFICATION); + services.put(AUTOMATION_IO); + services.put(BATTERY_SERVICE); + services.put(BLOOD_PRESSURE); + services.put(BODY_COMPOSITION); + services.put(BOND_MANAGEMENT); + services.put(CONTINUOUS_GLUCOSE_MONITORING); + services.put(CURRENT_TIME); + services.put(CYCLING_POWER); + services.put(CYCLING_SPEED_AND_CADENCE); + services.put(DEVICE_INFORMATION); + services.put(ENVIRONMENTAL_SENSING); + services.put(FITNESS_MACHINE); + services.put(GENERIC_ACCESS); + services.put(GENERIC_ATTRIBUTE); + services.put(GLUCOSE); + services.put(HEALTH_THERMOMETER); + services.put(HEART_RATE); + services.put(HTTP_PROXY); + services.put(HUMAN_INTERFACE_DEVICE); + services.put(IMMEDIATE_ALERT); + services.put(INDOOR_POSITIONING); + services.put(INTERNET_PROTOCOL_SUPPORT); + services.put(LINK_LOSS); + services.put(LOCATION_AND_NAVIGATION); + services.put(MESH_PROVISIONING); + services.put(MESH_PROXY); + services.put(NEXT_DST_CHANGE); + services.put(OBJECT_TRANSFER); + services.put(PHONE_ALERT_STATUS); + services.put(PULSE_OXIMETER); + services.put(REFERENCE_TIME_UPDATE); + services.put(RUNNING_SPEED_AND_CADENCE); + services.put(SCAN_PARAMETERS); + services.put(TRANSPORT_DISCOVERY); + services.put(TX_POWER); + services.put(USER_DATA); + services.put(WEIGHT_SCALE); + } + + /** + * Looks up the service for the given UUID. If the UUID is not found, a lookup in the smartgattlib is performed. + * + * @param uuid the UUID to look for + * @return the corresponding service or the DEFAULT service of smartgattlib if UUID is not found + */ + public static Service lookup(UUID uuid) + { + Service result = services.get(uuid); + return result == null ? DEFAULT : result; + } +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java new file mode 100644 index 0000000..721c714 --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java @@ -0,0 +1,51 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractReadWriteAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class Age extends AbstractReadWriteAttribute +{ + + public static final Characteristic CHARACTERISTIC = Characteristics.AGE; + + private Short age; + + public Short getAge() + { + return age; + } + + public String getAgeUnit() + { + return ""; + } + + public Age(Short age) + { + this.age = age; + GattByteBuffer bb = GattByteBuffer.allocate(1); + bb.putUint8(age); + this.data = bb.array(); + } + + public Age(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + age = bb.getUint8(); + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "Age: " + "age = " + getAge() + " " + getAgeUnit(); + } +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java new file mode 100644 index 0000000..30c46c9 --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java @@ -0,0 +1,43 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractReadAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class Appearance extends AbstractReadAttribute +{ + + public static final Characteristic CHARACTERISTIC = Characteristics.APPEARANCE; + + private Short category; + + public Short getCategory() + { + return category; + } + + public String getCategoryUnit() + { + return ""; + } + + public Appearance(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + category = bb.getInt16(); + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "Appearance: " + "category = " + getCategory() + " " + getCategoryUnit(); + } +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java new file mode 100644 index 0000000..f3eef83 --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java @@ -0,0 +1,43 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractReadAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class BatteryLevel extends AbstractReadAttribute +{ + + public static final Characteristic CHARACTERISTIC = Characteristics.BATTERY_LEVEL; + + private Double level; + + public Double getLevel() + { + return level; + } + + public String getLevelUnit() + { + return "%"; + } + + public BatteryLevel(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + level = new Double(bb.getUint8()); + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "Battery Level: " + "level = " + getLevel() + " " + getLevelUnit(); + } +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java new file mode 100644 index 0000000..ad5b0f9 --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java @@ -0,0 +1,79 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractReadWriteAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class DateOfBirth extends AbstractReadWriteAttribute +{ + + public static final Characteristic CHARACTERISTIC = Characteristics.DATE_OF_BIRTH; + + private Integer year; + private Short month; + private Short day; + + public Integer getYear() + { + return year; + } + + public String getYearUnit() + { + return ""; + } + + public Short getMonth() + { + return month; + } + + public String getMonthUnit() + { + return ""; + } + + public Short getDay() + { + return day; + } + + public String getDayUnit() + { + return ""; + } + + public DateOfBirth(Integer year, Short month, Short day) + { + this.year = year; + this.month = month; + this.day = day; + GattByteBuffer bb = GattByteBuffer.allocate(4); + bb.putUint16(year); + bb.putUint8(month); + bb.putUint8(day); + this.data = bb.array(); + } + + public DateOfBirth(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + year = bb.getUint16(); + month = bb.getUint8(); + day = bb.getUint8(); + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "Date of Birth: " + "year = " + getYear() + " " + getYearUnit() + ", " + "month = " + getMonth() + " " + getMonthUnit() + ", " + "day = " + getDay() + " " + getDayUnit(); + } +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java new file mode 100644 index 0000000..54e163d --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java @@ -0,0 +1,43 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractReadAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class DeviceName extends AbstractReadAttribute +{ + + public static final Characteristic CHARACTERISTIC = Characteristics.DEVICE_NAME; + + private String name; + + public String getName() + { + return name; + } + + public String getNameUnit() + { + return ""; + } + + public DeviceName(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + name = bb.getString(); + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "Device Name: " + "name = " + getName() + " " + getNameUnit(); + } +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/EnumGender.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/EnumGender.java new file mode 100644 index 0000000..649d95d --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/EnumGender.java @@ -0,0 +1,49 @@ +package com.movisens.smartgattlib.attributes; + +public enum EnumGender +{ + MALE ((short)0, "Male"), + FEMALE ((short)1, "Female"), + UNSPECIFIED ((short)2, "Unspecified"), + INVALID ((short)3, "invalid"); + + private final short value; + private final String name; + + EnumGender(short value, String name) + { + this.value = value; + this.name = name; + } + + public short getValue() + { + return value; + } + + public String getName() + { + return name; + } + + public static EnumGender getByValue(short value) + { + switch (value) + { + case 0: + return EnumGender.MALE; + case 1: + return EnumGender.FEMALE; + case 2: + return EnumGender.UNSPECIFIED; + default: + return EnumGender.INVALID; + } + } + + @Override + public String toString() + { + return name; + } +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java new file mode 100644 index 0000000..b82fca7 --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java @@ -0,0 +1,43 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractReadAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class FirmwareRevisionString extends AbstractReadAttribute +{ + + public static final Characteristic CHARACTERISTIC = Characteristics.FIRMWARE_REVISION_STRING; + + private String firmware_Revision; + + public String getFirmware_Revision() + { + return firmware_Revision; + } + + public String getFirmware_RevisionUnit() + { + return ""; + } + + public FirmwareRevisionString(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + firmware_Revision = bb.getString(); + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "Firmware Revision String: " + "firmware_Revision = " + getFirmware_Revision() + " " + getFirmware_RevisionUnit(); + } +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java new file mode 100644 index 0000000..c0d0ddf --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java @@ -0,0 +1,51 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractReadWriteAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class Gender extends AbstractReadWriteAttribute +{ + + public static final Characteristic CHARACTERISTIC = Characteristics.GENDER; + + private EnumGender gender; + + public EnumGender getGender() + { + return gender; + } + + public String getGenderUnit() + { + return ""; + } + + public Gender(EnumGender gender) + { + this.gender = gender; + GattByteBuffer bb = GattByteBuffer.allocate(1); + bb.putUint8(gender.getValue()); + this.data = bb.array(); + } + + public Gender(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + gender = EnumGender.getByValue(bb.getUint8()); + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "Gender: " + "gender = " + getGender() + " " + getGenderUnit(); + } +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java new file mode 100644 index 0000000..5957de3 --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java @@ -0,0 +1,51 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractReadWriteAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class Height extends AbstractReadWriteAttribute +{ + + public static final Characteristic CHARACTERISTIC = Characteristics.HEIGHT; + + private Double heigt; + + public Double getHeigt() + { + return heigt; + } + + public String getHeigtUnit() + { + return "m"; + } + + public Height(Double heigt) + { + this.heigt = heigt; + GattByteBuffer bb = GattByteBuffer.allocate(2); + bb.putUint16(new Double(heigt / 0.01).intValue()); + this.data = bb.array(); + } + + public Height(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + heigt = ((double)bb.getUint16()) * 0.01; + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "Height: " + "heigt = " + getHeigt() + " " + getHeigtUnit(); + } +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java new file mode 100644 index 0000000..7ef0dac --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java @@ -0,0 +1,43 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractReadAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class ManufacturerNameString extends AbstractReadAttribute +{ + + public static final Characteristic CHARACTERISTIC = Characteristics.MANUFACTURER_NAME_STRING; + + private String manufacturer_Name; + + public String getManufacturer_Name() + { + return manufacturer_Name; + } + + public String getManufacturer_NameUnit() + { + return ""; + } + + public ManufacturerNameString(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + manufacturer_Name = bb.getString(); + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "Manufacturer Name String: " + "manufacturer_Name = " + getManufacturer_Name() + " " + getManufacturer_NameUnit(); + } +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java new file mode 100644 index 0000000..88f864a --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java @@ -0,0 +1,43 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractReadAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class ModelNumberString extends AbstractReadAttribute +{ + + public static final Characteristic CHARACTERISTIC = Characteristics.MODEL_NUMBER_STRING; + + private String model_Number; + + public String getModel_Number() + { + return model_Number; + } + + public String getModel_NumberUnit() + { + return ""; + } + + public ModelNumberString(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + model_Number = bb.getString(); + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "Model Number String: " + "model_Number = " + getModel_Number() + " " + getModel_NumberUnit(); + } +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java new file mode 100644 index 0000000..b532522 --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java @@ -0,0 +1,43 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractReadAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class SerialNumberString extends AbstractReadAttribute +{ + + public static final Characteristic CHARACTERISTIC = Characteristics.SERIAL_NUMBER_STRING; + + private String serial_Number; + + public String getSerial_Number() + { + return serial_Number; + } + + public String getSerial_NumberUnit() + { + return ""; + } + + public SerialNumberString(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + serial_Number = bb.getString(); + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "Serial Number String: " + "serial_Number = " + getSerial_Number() + " " + getSerial_NumberUnit(); + } +} diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java new file mode 100644 index 0000000..f1779ac --- /dev/null +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java @@ -0,0 +1,51 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractReadWriteAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class Weight extends AbstractReadWriteAttribute +{ + + public static final Characteristic CHARACTERISTIC = Characteristics.WEIGHT; + + private Double weight; + + public Double getWeight() + { + return weight; + } + + public String getWeightUnit() + { + return "kg"; + } + + public Weight(Double weight) + { + this.weight = weight; + GattByteBuffer bb = GattByteBuffer.allocate(2); + bb.putUint16(new Double(weight / 0.005).intValue()); + this.data = bb.array(); + } + + public Weight(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + weight = ((double)bb.getUint16()) * 0.005; + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "Weight: " + "weight = " + getWeight() + " " + getWeightUnit(); + } +} diff --git a/src/main/java/com/movisens/smartgattlib/Characteristic.java b/src/main/java/com/movisens/smartgattlib/Characteristic.java deleted file mode 100644 index 9413e79..0000000 --- a/src/main/java/com/movisens/smartgattlib/Characteristic.java +++ /dev/null @@ -1,233 +0,0 @@ -package com.movisens.smartgattlib; - -import java.util.HashMap; -import java.util.UUID; - -public class Characteristic { - public static final UUID ALERT_CATEGORY_ID = new UUID((0x2A43L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID ALERT_CATEGORY_ID_BIT_MASK = new UUID((0x2A42L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID ALERT_LEVEL = new UUID((0x2A06L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID ALERT_NOTIFICATION_CONTROL_POINT = new UUID((0x2A44L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID ALERT_STATUS = new UUID((0x2A3FL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID APPEARANCE = new UUID((0x2A01L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID BATTERY_LEVEL = new UUID((0x2A19L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID BLOOD_PRESSURE_FEATURE = new UUID((0x2A49L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID BLOOD_PRESSURE_MEASUREMENT = new UUID((0x2A35L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID BODY_SENSOR_LOCATION = new UUID((0x2A38L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID BOOT_KEYOBARD_INPUT_REPORT = new UUID((0x2A22L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID BOOT_KEYOBARD_OUTPUT_REPORT = new UUID((0x2A32L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID BOOT_MOUSE_INPUT_REPORT = new UUID((0x2A33L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID CSC_FEATURE = new UUID((0x2A5CL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID CSC_MEASUREMENT = new UUID((0x2A5BL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID CURRENT_TIME = new UUID((0x2A2BL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID CYCLING_POWER_CONTROL_POINT = new UUID((0x2A66L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID CYCLING_POWER_FEATURE = new UUID((0x2A65L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID CYCLING_POWER_MEASUREMENT = new UUID((0x2A63L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID CYCLING_POWER_VECTOR = new UUID((0x2A64L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID DATE_TIME = new UUID((0x2A08L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID DAY_DATE_TIME = new UUID((0x2A0AL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID DAY_OF_WEEK = new UUID((0x2A09L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID DEVICE_NAME = new UUID((0x2A00L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID DST_OFFSET = new UUID((0x2A0DL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID EXACT_TIME_256 = new UUID((0x2A0CL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID FIRMWARE_REVISION_STRING = new UUID((0x2A26L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID GLUCOSE_FEATURE = new UUID((0x2A51L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID GLUCOSE_MEASUREMENT = new UUID((0x2A18L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID GLUCOSE_MEASUREMENT_CONTROL = new UUID((0x2A34L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID HARDWARE_REVISION_STRING = new UUID((0x2A27L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID HEART_RATE_CONTROL_POINT = new UUID((0x2A39L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID HEART_RATE_MEASUREMENT = new UUID((0x2A37L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID HID_CONTROL_POINT = new UUID((0x2A4CL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID HID_INFORMATION = new UUID((0x2A4AL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID IEEE11073_20601_REGULATORY_CERTIFICATION_DATA_LIST = new UUID((0x2A2AL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID INTERMEDIATE_CUFF_PRESSURE = new UUID((0x2A36L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID INTERMEDIATE_TEMPERATURE = new UUID((0x2A1EL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID LN_CONTROL_POINT = new UUID((0x2A6BL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID LN_FEATURE = new UUID((0x2A6AL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID LOCAL_TIME_INFORMATION = new UUID((0x2A0FL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID LOCATION_AND_SPEED = new UUID((0x2A67L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID MANUFACTURER_NAME_STRING = new UUID((0x2A29L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID MEASUREMENT_INTERVAL = new UUID((0x2A21L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID MODEL_NUMBER_STRING = new UUID((0x2A24L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID NAVIGATION = new UUID((0x2A68L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID NEW_ALERT = new UUID((0x2A46L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID PERIPERAL_PREFFERED_CONNECTION_PARAMETERS = new UUID((0x2A04L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID PERIPHERAL_PRIVACY_FLAG = new UUID((0x2A02L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID PN_PID = new UUID((0x2A50L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID POSITION_QUALITY = new UUID((0x2A69L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID PROTOCOL_MODE = new UUID((0x2A4EL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID RECONNECTION_ADDRESS = new UUID((0x2A03L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID RECORD_ACCESS_CONTROL_POINT = new UUID((0x2A52L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID REFERENCE_TIME_INFORMATION = new UUID((0x2A14L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID REPORT = new UUID((0x2A4DL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID REPORT_MAP = new UUID((0x2A4BL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID RINGER_CONTROL_POINT = new UUID((0x2A40L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID RINGER_SETTING = new UUID((0x2A41L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID RSC_FEATURE = new UUID((0x2A54L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID RSC_MEASUREMENT = new UUID((0x2A53L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SC_CONTROL_POINT = new UUID((0x2A55L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SCAN_INTERVAL_WINDOW = new UUID((0x2A4FL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SCAN_REFRESH = new UUID((0x2A31L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SENSOR_LOCATION = new UUID((0x2A5DL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SERIAL_NUMBER_STRING = new UUID((0x2A25L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SERVICE_CHANGED = new UUID((0x2A05L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SOFTWARE_REVISION_STRING = new UUID((0x2A28L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SUPPORTED_NEW_ALERT_CATEGORY = new UUID((0x2A47L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SUPPORTED_UNREAD_ALERT_CATEGORY = new UUID((0x2A48L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SYSTEM_ID = new UUID((0x2A23L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TEMPERATURE_MEASUREMENT = new UUID((0x2A1CL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TEMPERATURE_TYPE = new UUID((0x2A1DL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TIME_ACCURACY = new UUID((0x2A12L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TIME_SOURCE = new UUID((0x2A13L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TIME_UPDATE_CONTROL_POINT = new UUID((0x2A16L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TIME_UPDATE_STATE = new UUID((0x2A17L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TIME_WITH_DST = new UUID((0x2A11L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TIME_ZONE = new UUID((0x2A0EL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TX_POWER_LEVEL = new UUID((0x2A07L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID UNREAD_ALERT_STATUS = new UUID((0x2A45L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID AGGREGATE_INPUT = new UUID((0x2A5AL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID ANALOG_INPUT = new UUID((0x2A58L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID ANALOG_OUTPUT = new UUID((0x2A59L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID DIGITAL_INPUT = new UUID((0x2A56L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID DIGITAL_OUTPUT = new UUID((0x2A57L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID EXACT_TIME_100 = new UUID((0x2A0BL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID NETWORK_AVAILABILITY = new UUID((0x2A3EL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SCIENTIFIC_TEMPERATURE_IN_CELSIUS = new UUID((0x2A3CL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SECONDARY_TIME_ZONE = new UUID((0x2A10L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID STRING = new UUID((0x2A3DL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TEMPERATURE_IN_CELSIUS = new UUID((0x2A1FL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TEMPERATURE_IN_FAHRENHEIT = new UUID((0x2A20L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TIME_BROADCAST = new UUID((0x2A15L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID BATTERY_LEVEL_STATE = new UUID((0x2A1BL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID BATTERY_POWER_STATE = new UUID((0x2A1AL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID PULSE_OXIMETRY_CONTINUOUS_MEASUREMENT = new UUID((0x2A5FL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID PULSE_OXIMETRY_CONTROL_POINT = new UUID((0x2A62L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID PULSE_OXIMETRY_FEATURES = new UUID((0x2A61L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID PULSE_OXIMETRY_PULSATILE_EVENT = new UUID((0x2A60L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID PULSE_OXIMETRY_SPOT_CHECK_MEASUREMENT = new UUID((0x2A5EL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID RECORD_ACCESS_CONTROL_POINT_TESTVERSION = new UUID((0x2A52L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID REMOVABLE = new UUID((0x2A3AL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SERVICE_REQUIRED = new UUID((0x2A3BL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID AGE = new UUID((0x2A80L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID GENDER = new UUID((0x2A8CL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID WEIGHT = new UUID((0x2A98L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID HEIGHT = new UUID((0x2A8EL << 32) | 0x1000, GattUtils.leastSigBits); - - private static HashMap attributes = new HashMap(); - - static { - attributes.put(ALERT_CATEGORY_ID, "Alert Category ID"); - attributes.put(ALERT_CATEGORY_ID_BIT_MASK, "Alert Category ID Bit Mask"); - attributes.put(ALERT_LEVEL, "Alert Level"); - attributes.put(ALERT_NOTIFICATION_CONTROL_POINT, "Alert Notification Control Point"); - attributes.put(ALERT_STATUS, "Alert Status"); - attributes.put(APPEARANCE, "Appearance"); - attributes.put(BATTERY_LEVEL, "Battery Level"); - attributes.put(BLOOD_PRESSURE_FEATURE, "Blood Pressure Feature"); - attributes.put(BLOOD_PRESSURE_MEASUREMENT, "Blood Pressure Measurement"); - attributes.put(BODY_SENSOR_LOCATION, "Body Sensor Location"); - attributes.put(BOOT_KEYOBARD_INPUT_REPORT, "Boot Keyboard Input Report"); - attributes.put(BOOT_KEYOBARD_OUTPUT_REPORT, "Boot Keyboard Output Report"); - attributes.put(BOOT_MOUSE_INPUT_REPORT, "Boot Mouse Input Report"); - attributes.put(CSC_FEATURE, "CSC Feature"); - attributes.put(CSC_MEASUREMENT, "CSC Measurement"); - attributes.put(CURRENT_TIME, "Current Time"); - attributes.put(CYCLING_POWER_CONTROL_POINT, "Cycling Power Control Point"); - attributes.put(CYCLING_POWER_FEATURE, "Cycling Power Feature"); - attributes.put(CYCLING_POWER_MEASUREMENT, "Cycling Power Measurement"); - attributes.put(CYCLING_POWER_VECTOR, "Cycling Power Vector"); - attributes.put(DATE_TIME, "Date Time"); - attributes.put(DAY_DATE_TIME, "Day Date Time"); - attributes.put(DAY_OF_WEEK, "Day of Week"); - attributes.put(DEVICE_NAME, "Device Name"); - attributes.put(DST_OFFSET, "DST Offset"); - attributes.put(EXACT_TIME_256, "Exact Time 256"); - attributes.put(FIRMWARE_REVISION_STRING, "Firmware Revision String"); - attributes.put(GLUCOSE_FEATURE, "Glucose Feature"); - attributes.put(GLUCOSE_MEASUREMENT, "Glucose Measurement"); - attributes.put(GLUCOSE_MEASUREMENT_CONTROL, "Glucose Measurement Context"); - attributes.put(HARDWARE_REVISION_STRING, "Hardware Revision String"); - attributes.put(HEART_RATE_CONTROL_POINT, "Heart Rate Control Point"); - attributes.put(HEART_RATE_MEASUREMENT, "Heart Rate Measurement"); - attributes.put(HID_CONTROL_POINT, "HID Control Point"); - attributes.put(HID_INFORMATION, "HID Information"); - attributes.put(IEEE11073_20601_REGULATORY_CERTIFICATION_DATA_LIST, "IEEE 11073-20601 Regulatory Certification Data List"); - attributes.put(INTERMEDIATE_CUFF_PRESSURE, "Intermediate Cuff Pressure"); - attributes.put(INTERMEDIATE_TEMPERATURE, "Intermediate Temperature"); - attributes.put(LN_CONTROL_POINT, "LN Control Point"); - attributes.put(LN_FEATURE, "LN Feature"); - attributes.put(LOCAL_TIME_INFORMATION, "Local Time Information"); - attributes.put(LOCATION_AND_SPEED, "Location and Speed"); - attributes.put(MANUFACTURER_NAME_STRING, "Manufacturer Name String"); - attributes.put(MEASUREMENT_INTERVAL, "Measurement Interval"); - attributes.put(MODEL_NUMBER_STRING, "Model Number String"); - attributes.put(NAVIGATION, "Navigation"); - attributes.put(NEW_ALERT, "New Alert"); - attributes.put(PERIPERAL_PREFFERED_CONNECTION_PARAMETERS, "Peripheral Preferred Connection Parameters"); - attributes.put(PERIPHERAL_PRIVACY_FLAG, "Peripheral Privacy Flag"); - attributes.put(PN_PID, "PnP ID"); - attributes.put(POSITION_QUALITY, "Position Quality"); - attributes.put(PROTOCOL_MODE, "Protocol Mode"); - attributes.put(RECONNECTION_ADDRESS, "Reconnection Address"); - attributes.put(RECORD_ACCESS_CONTROL_POINT, "Record Access Control Point"); - attributes.put(REFERENCE_TIME_INFORMATION, "Reference Time Information"); - attributes.put(REPORT, "Report"); - attributes.put(REPORT_MAP, "Report Map"); - attributes.put(RINGER_CONTROL_POINT, "Ringer Control Point"); - attributes.put(RINGER_SETTING, "Ringer Setting"); - attributes.put(RSC_FEATURE, "RSC Feature"); - attributes.put(RSC_MEASUREMENT, "RSC Measurement"); - attributes.put(SC_CONTROL_POINT, "SC Control Point"); - attributes.put(SCAN_INTERVAL_WINDOW, "Scan Interval Window"); - attributes.put(SCAN_REFRESH, "Scan Refresh"); - attributes.put(SENSOR_LOCATION, "Sensor Location"); - attributes.put(SERIAL_NUMBER_STRING, "Serial Number String"); - attributes.put(SERVICE_CHANGED, "Service Changed"); - attributes.put(SOFTWARE_REVISION_STRING, "Software Revision String"); - attributes.put(SUPPORTED_NEW_ALERT_CATEGORY, "Supported New Alert Category"); - attributes.put(SUPPORTED_UNREAD_ALERT_CATEGORY, "Supported Unread Alert Category"); - attributes.put(SYSTEM_ID, "System ID"); - attributes.put(TEMPERATURE_MEASUREMENT, "Temperature Measurement"); - attributes.put(TEMPERATURE_TYPE, "Temperature Type"); - attributes.put(TIME_ACCURACY, "Time Accuracy"); - attributes.put(TIME_SOURCE, "Time Source"); - attributes.put(TIME_UPDATE_CONTROL_POINT, "Time Update Control Point"); - attributes.put(TIME_UPDATE_STATE, "Time Update State"); - attributes.put(TIME_WITH_DST, "Time with DST"); - attributes.put(TIME_ZONE, "Time Zone"); - attributes.put(TX_POWER_LEVEL, "Tx Power Level"); - attributes.put(UNREAD_ALERT_STATUS, "Unread Alert Status"); - attributes.put(AGGREGATE_INPUT, "Aggregate Input"); - attributes.put(ANALOG_INPUT, "Analog Input"); - attributes.put(ANALOG_OUTPUT, "Analog Output"); - attributes.put(DIGITAL_INPUT, "Digital Input"); - attributes.put(DIGITAL_OUTPUT, "Digital Output"); - attributes.put(EXACT_TIME_100, "Exact Time 100"); - attributes.put(NETWORK_AVAILABILITY, "Network Availability"); - attributes.put(SCIENTIFIC_TEMPERATURE_IN_CELSIUS, "Scientific Temperature in Celsius"); - attributes.put(SECONDARY_TIME_ZONE, "Secondary Time Zone"); - attributes.put(STRING, "String"); - attributes.put(TEMPERATURE_IN_CELSIUS, "Temperature in Celsius"); - attributes.put(TEMPERATURE_IN_FAHRENHEIT, "Temperature in Fahrenheit"); - attributes.put(TIME_BROADCAST, "Time Broadcast"); - attributes.put(BATTERY_LEVEL_STATE, "Battery Level State"); - attributes.put(BATTERY_POWER_STATE, "Battery Power State"); - attributes.put(PULSE_OXIMETRY_CONTINUOUS_MEASUREMENT, "Pulse Oximetry Continuous Measurement"); - attributes.put(PULSE_OXIMETRY_CONTROL_POINT, "Pulse Oximetry Control Point"); - attributes.put(PULSE_OXIMETRY_FEATURES, "Pulse Oximetry Features"); - attributes.put(PULSE_OXIMETRY_PULSATILE_EVENT, "Pulse Oximetry Pulsatile Event"); - attributes.put(PULSE_OXIMETRY_SPOT_CHECK_MEASUREMENT, "Pulse Oximetry Spot-Check Measurement"); - attributes.put(RECORD_ACCESS_CONTROL_POINT_TESTVERSION, "Record Access Control point (Test Version)"); - attributes.put(REMOVABLE, "Removable"); - attributes.put(SERVICE_REQUIRED, "Service Required"); - attributes.put(AGE, "Age of Participant"); - attributes.put(GENDER, "Gender of participant"); - attributes.put(WEIGHT, "Weight of participant"); - attributes.put(HEIGHT, "Height of participant"); - } - - public static String lookup(UUID uuid, String defaultName) { - String name = attributes.get(uuid); - return name == null ? defaultName : name; - } -} \ No newline at end of file diff --git a/src/main/java/com/movisens/smartgattlib/Descriptor.java b/src/main/java/com/movisens/smartgattlib/Descriptor.java deleted file mode 100644 index 85f2932..0000000 --- a/src/main/java/com/movisens/smartgattlib/Descriptor.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.movisens.smartgattlib; - -import java.util.HashMap; -import java.util.UUID; - -public class Descriptor { - public static final UUID CHARACTERISTIC_EXTENDED_PROPERTIES = new UUID((0x2900L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID CHARACTERISTIC_USER_DESCRIPTION = new UUID((0x2901L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID CLIENT_CHARACTERISTIC_CONFIGURATION = new UUID((0x2902L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SERVER_CHARACTERISTIC_CONFIGURATION = new UUID((0x2903L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID CHARACTERISTIC_PRESENTATION_FORMAT = new UUID((0x2904L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID CHARACTERISTIC_AGGREGATE_FORMAT = new UUID((0x2905L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID VALID_RANGE = new UUID((0x2906L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID EXTERNAL_REPORT_REFERENCE = new UUID((0x2907L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID REPORT_REFERENCE = new UUID((0x2908L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID NUMBER_OF_DIGITALS = new UUID((0x2909L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TRIGGER_SETTING = new UUID((0x290AL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TEST_COMPLEX_BITFIELD = new UUID((0L << 32) | 0x1000, GattUtils.leastSigBits); - - private static HashMap attributes = new HashMap(); - static { - attributes.put(CHARACTERISTIC_EXTENDED_PROPERTIES, "Characteristic Extended Properties"); - attributes.put(CHARACTERISTIC_USER_DESCRIPTION, "Characteristic User Description"); - attributes.put(CLIENT_CHARACTERISTIC_CONFIGURATION, "Client Characteristic Configuration"); - attributes.put(SERVER_CHARACTERISTIC_CONFIGURATION, "Server Characteristic Configuration"); - attributes.put(CHARACTERISTIC_PRESENTATION_FORMAT, "Characteristic Presentation Format"); - attributes.put(CHARACTERISTIC_AGGREGATE_FORMAT, "Characteristic Aggregate Format"); - attributes.put(VALID_RANGE, "Valid Range"); - attributes.put(EXTERNAL_REPORT_REFERENCE, "External Report Reference"); - attributes.put(REPORT_REFERENCE, "Report Reference"); - attributes.put(NUMBER_OF_DIGITALS, "Number of Digitals"); - attributes.put(TRIGGER_SETTING, "Trigger Setting"); - attributes.put(TEST_COMPLEX_BITFIELD, "Test Complex BitField"); - } - - public static String lookup(UUID uuid, String defaultName) { - String name = attributes.get(uuid); - return name == null ? defaultName : name; - } -} \ No newline at end of file diff --git a/src/main/java/com/movisens/smartgattlib/GattByteBuffer.java b/src/main/java/com/movisens/smartgattlib/GattByteBuffer.java deleted file mode 100644 index bbb180d..0000000 --- a/src/main/java/com/movisens/smartgattlib/GattByteBuffer.java +++ /dev/null @@ -1,185 +0,0 @@ -package com.movisens.smartgattlib; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; - -public class GattByteBuffer { - - public static GattByteBuffer allocate(int i) { - GattByteBuffer result = new GattByteBuffer(); - result.buffer = ByteBuffer.allocate(i); - result.buffer.order(ByteOrder.LITTLE_ENDIAN); - return result; - } - - public static GattByteBuffer wrap(byte[] byteArray) { - GattByteBuffer result = new GattByteBuffer(); - result.buffer = ByteBuffer.wrap(byteArray); - result.buffer.order(ByteOrder.LITTLE_ENDIAN); - return result; - } - - private ByteBuffer buffer; - - public byte[] array() { - return buffer.array(); - } - - public final int capacity() { - return buffer.capacity(); - } - - public void getInt8(byte[] value, int i, int j) { - buffer.get(value, i, j); - } - - public void getUint8(short[] value, int offset, int length) { - for (int i = 0; i < length; i++) { - value[i + offset] = getUint8(); - } - } - - public Boolean getBoolean() { - if (buffer.get() == (byte) 0) { - return false; - } else { - return true; - } - } - - public Float getFloat32() { - return buffer.getFloat(); - } - - public Short getInt16() { - return buffer.getShort(); - } - - public Integer getInt32() { - return buffer.getInt(); - } - - public Long getInt64() { - return buffer.getLong(); - } - - public Byte getInt8() { - return buffer.get(); - } - - public String getString() { - String result = ""; - byte c; - - while ((c = buffer.get()) != 0) { - result += (char) c; - } - - return result; - } - - public Integer getUint16() { - int result = buffer.getShort(); - if (result < 0) { - result += ((int) 1) << 16; - } - return result; - } - - public Long getUint32() { - long result = buffer.getInt(); - if (result < 0) { - result += ((long) 1) << 32; - } - return result; - } - - public Short getUint8() { - short result = buffer.get(); - if (result < 0) { - result += ((short) 1) << 8; - } - return result; - } - - public GattByteBuffer position(int i) { - buffer.position(i); - return this; - } - - public GattByteBuffer putUint8(short[] value, int offset, int length) { - for (int i = 0; i < length; i++) { - putUint8(value[i + offset]); - } - return this; - } - - public GattByteBuffer putInt8(byte[] value, int i, int j) { - buffer.put(value, i, j); - return this; - } - - public GattByteBuffer putBoolean(boolean doEnable) { - if (doEnable) { - buffer.put((byte) 1); - } else { - buffer.put((byte) 0); - } - return this; - } - - public GattByteBuffer putFloat32(Float value) { - buffer.putFloat(value); - return this; - } - - public GattByteBuffer putInt16(short value) { - buffer.putShort(value); - return this; - } - - public GattByteBuffer putInt32(int value) { - buffer.putInt(value); - return this; - } - - public GattByteBuffer putInt64(long value) { - buffer.putLong(value); - return this; - } - - public GattByteBuffer putInt8(byte value) { - buffer.put(value); - return this; - } - - public GattByteBuffer putString(String value) { - buffer.put(value.getBytes()); - buffer.put((byte) 0); - return this; - } - - public GattByteBuffer putUint16(int value) { - buffer.putShort((short) value); - return this; - } - - public GattByteBuffer putUint32(long value) { - buffer.putInt((int) value); - return this; - } - - public GattByteBuffer putUint8(short value) { - buffer.put((byte) value); - return this; - } - - public GattByteBuffer rewind() { - buffer.rewind(); - return this; - } - - public boolean hasRemaining() { - return buffer.hasRemaining(); - } -} diff --git a/src/main/java/com/movisens/smartgattlib/Service.java b/src/main/java/com/movisens/smartgattlib/Service.java deleted file mode 100644 index 33a2845..0000000 --- a/src/main/java/com/movisens/smartgattlib/Service.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.movisens.smartgattlib; - -import java.util.HashMap; -import java.util.UUID; - -public class Service { - public static final UUID ALERT_NOTIFICATION_SERVICE = new UUID((0x1811L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID BATTERY_SERVICE = new UUID((0x180FL << 32) | 0x1000,GattUtils.leastSigBits); - public static final UUID BLOOD_PRESSURE = new UUID((0x1810L << 32) | 0x1000,GattUtils.leastSigBits); - public static final UUID CURRENT_TIME_SERVICE = new UUID((0x1805L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID CYCLING_POWER = new UUID((0x1818L << 32) | 0x1000,GattUtils.leastSigBits); - public static final UUID CYCLING_SPEED_AND_CADENCE = new UUID((0x1816L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID DEVICE_INFORMATION = new UUID((0x180AL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID GENERIC_ACCESS = new UUID((0x1800L << 32) | 0x1000,GattUtils.leastSigBits); - public static final UUID GENERIC_ATTRIBUTE = new UUID((0x1801L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID GLUCOSE = new UUID((0x1808L << 32) | 0x1000,GattUtils.leastSigBits); - public static final UUID HEALTH_THERMOMETER = new UUID((0x1809L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID HEART_RATE = new UUID((0x180DL << 32) | 0x1000,GattUtils.leastSigBits); - public static final UUID HUMAN_INTERFACE_DEVICE = new UUID((0x1812L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID IMMEDIATE_ALERT = new UUID((0x1802L << 32) | 0x1000,GattUtils.leastSigBits); - public static final UUID LINK_LOSS = new UUID((0x1803L << 32) | 0x1000,GattUtils.leastSigBits); - public static final UUID LOCATION_AND_NAVIGATION = new UUID((0x1819L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID NEXT_DST_CHANGE_SERVICE = new UUID((0x1807L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID PHONE_ALERT_STATUS_SERVICE = new UUID((0x180EL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID REFERENCE_TIME_UPDATE_SERVICE = new UUID((0x1806L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID RUNNING_SPEED_AND_CADENCE = new UUID((0x1814L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID SCAN_PARAMETERS = new UUID((0x1813L << 32) | 0x1000,GattUtils.leastSigBits); - public static final UUID TX_POWER = new UUID((0x1804L << 32) | 0x1000,GattUtils.leastSigBits); - public static final UUID AUTOMATION_IO = new UUID((0x1815L << 32) | 0x1000,GattUtils.leastSigBits); - public static final UUID BATTERY_SERVICE_1_1 = new UUID((0x180FL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID IMMEDIATE_ALERT_SERVICE_1_1 = new UUID((0x1802L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID LINK_LOSS_SERVICE_1_1 = new UUID((0x1803L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID NETWORK_AVAILABILITY_SERVICE = new UUID((0x180BL << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID TX_POWER_SERVICE_1_1 = new UUID((0x1804L << 32) | 0x1000, GattUtils.leastSigBits); - public static final UUID USER_DATA_SERVICE = new UUID((0x181cL << 32) | 0x1000, GattUtils.leastSigBits); - - private static HashMap attributes = new HashMap(); - static { - attributes.put(ALERT_NOTIFICATION_SERVICE, "Alert Notification Service"); - attributes.put(BATTERY_SERVICE, "Battery Service"); - attributes.put(BLOOD_PRESSURE, "Blood Pressure"); - attributes.put(CURRENT_TIME_SERVICE, "Current Time Service"); - attributes.put(CYCLING_POWER, "Cycling Power"); - attributes.put(CYCLING_SPEED_AND_CADENCE, "Cycling Speed and Cadence"); - attributes.put(DEVICE_INFORMATION, "Device Information"); - attributes.put(GENERIC_ACCESS, "Generic Access"); - attributes.put(GENERIC_ATTRIBUTE, "Generic Attribute"); - attributes.put(GLUCOSE, "Glucose"); - attributes.put(HEALTH_THERMOMETER, "Health Thermometer"); - attributes.put(HEART_RATE, "Heart Rate"); - attributes.put(HUMAN_INTERFACE_DEVICE, "Human Interface Device"); - attributes.put(IMMEDIATE_ALERT, "Immediate Alert"); - attributes.put(LINK_LOSS, "Link Loss"); - attributes.put(LOCATION_AND_NAVIGATION, "Location and Navigation"); - attributes.put(NEXT_DST_CHANGE_SERVICE, "Next DST Change Service"); - attributes.put(PHONE_ALERT_STATUS_SERVICE, "Phone Alert Status Service"); - attributes.put(REFERENCE_TIME_UPDATE_SERVICE, "Reference Time Update Service"); - attributes.put(RUNNING_SPEED_AND_CADENCE, "Running Speed and Cadence"); - attributes.put(SCAN_PARAMETERS, "Scan Parameters"); - attributes.put(TX_POWER, "Tx Power"); - attributes.put(AUTOMATION_IO, "Automation IO"); - attributes.put(BATTERY_SERVICE_1_1, "Battery Service v1.1"); - attributes.put(IMMEDIATE_ALERT_SERVICE_1_1, "Immediate Alert Service 1.1"); - attributes.put(LINK_LOSS_SERVICE_1_1, "Link Loss Service 1.1"); - attributes.put(NETWORK_AVAILABILITY_SERVICE, "Network Availability Service"); - attributes.put(TX_POWER_SERVICE_1_1, "Tx Power Service 1.1"); - attributes.put(USER_DATA_SERVICE, "User Data Service"); - } - - public static String lookup(UUID uuid, String defaultName) { - String name = attributes.get(uuid); - return name == null ? defaultName : name; - } -} \ No newline at end of file diff --git a/src/main/java/com/movisens/smartgattlib/attributes/BodySensorLocation.java b/src/main/java/com/movisens/smartgattlib/attributes/BodySensorLocation.java new file mode 100644 index 0000000..980de53 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/attributes/BodySensorLocation.java @@ -0,0 +1,71 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractReadAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class BodySensorLocation extends AbstractReadAttribute { + + public enum Location { + Other, Chest, Wrist, Finger, Hand, Ear_Lobe, Foot; + } + + public static final Characteristic CHARACTERISTIC = Characteristics.BODY_SENSOR_LOCATION; + + private Location location; + + public Location getLocation() + { + return location; + } + + public String getLocationUnit() + { + return ""; + } + + public BodySensorLocation(byte[] data) + { + this.data = data; + Location location = Location.Other; + int loc = GattByteBuffer.wrap(data).getUint8(); + + switch (loc) { + case 0: + location = Location.Other; + break; + case 1: + location = Location.Chest; + break; + case 2: + location = Location.Wrist; + break; + case 3: + location = Location.Finger; + break; + case 4: + location = Location.Hand; + break; + case 5: + location = Location.Ear_Lobe; + break; + case 6: + location = Location.Foot; + break; + } + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "BodySensorLocation: " + "location = " + getLocation() + " " + getLocationUnit(); + } + +} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java b/src/main/java/com/movisens/smartgattlib/attributes/CyclingSpeedCadenceMeasurement.java similarity index 76% rename from src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java rename to src/main/java/com/movisens/smartgattlib/attributes/CyclingSpeedCadenceMeasurement.java index 3390abb..614e8db 100644 --- a/src/main/java/com/movisens/smartgattlib/characteristics/CyclingSpeedCadenceMeasurement.java +++ b/src/main/java/com/movisens/smartgattlib/attributes/CyclingSpeedCadenceMeasurement.java @@ -1,10 +1,14 @@ -package com.movisens.smartgattlib.characteristics; +package com.movisens.smartgattlib.attributes; -import com.movisens.smartgattlib.GattByteBuffer; +import com.movisens.smartgattlib.Characteristics; import com.movisens.smartgattlib.GattUtils; -import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; +import com.movisens.smartgattlib.helper.AbstractReadAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; -public class CyclingSpeedCadenceMeasurement extends AbstractReadOnlyCharacteristic { +public class CyclingSpeedCadenceMeasurement extends AbstractReadAttribute { + + public static final Characteristic CHARACTERISTIC = Characteristics.CSC_MEASUREMENT; public static final int MAX_CUMULATIVE_CRANK_REVS = 65535; public static final long MAX_CUMULATIVE_WHEEL_REVS = 4294967295L; @@ -17,13 +21,9 @@ public class CyclingSpeedCadenceMeasurement extends AbstractReadOnlyCharacterist private int cumulativeCrankRevolutions; private int lastCrankEventTime; - public CyclingSpeedCadenceMeasurement(byte[] bytes) { - super(bytes); - } - - @Override - protected Number[] getValueForBytes(byte[] bytes) { - GattByteBuffer bb = GattByteBuffer.wrap(bytes); + public CyclingSpeedCadenceMeasurement(byte[] data) { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); byte flags = bb.getInt8(); wheelRevPresent = wheelRevPresent(flags); @@ -38,7 +38,6 @@ protected Number[] getValueForBytes(byte[] bytes) { cumulativeCrankRevolutions = bb.getUint16(); lastCrankEventTime = bb.getUint16(); } - return new Number[]{cumulativeWheelRevolutions, lastWheelEventTime, cumulativeCrankRevolutions, lastCrankEventTime}; } public boolean isWheelRevPresent() { @@ -54,7 +53,7 @@ public long getCumulativeWheelRevolutions() { } //unit has resolution of 1/1024s - public int getLastWheelEventTime() { + public int getWheelEventTime() { return lastWheelEventTime; } @@ -63,7 +62,7 @@ public int getCumulativeCrankRevolutions() { } //unit has resolution of 1/1024s - public int getLastCrankEventTime() { + public int getCrankEventTime() { return lastCrankEventTime; } @@ -75,6 +74,11 @@ private boolean crankRevPresent(byte flags) { return (flags & GattUtils.SECOND_BITMASK) != 0; } + @Override + public Characteristic getCharacteristic() { + return CHARACTERISTIC; + } + @Override public String toString() { return "SpeedCadenceMeasurement{" + @@ -86,4 +90,4 @@ public String toString() { ", lastCrankEventTime=" + lastCrankEventTime + '}'; } -} +} \ No newline at end of file diff --git a/src/main/java/com/movisens/smartgattlib/attributes/DefaultAttribute.java b/src/main/java/com/movisens/smartgattlib/attributes/DefaultAttribute.java new file mode 100644 index 0000000..c13afee --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/attributes/DefaultAttribute.java @@ -0,0 +1,43 @@ +package com.movisens.smartgattlib.attributes; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.helper.AbstractAttribute; +import com.movisens.smartgattlib.helper.Characteristic; + +public class DefaultAttribute extends AbstractAttribute +{ + + public DefaultAttribute(byte[] data) + { + this.data = data; + } + + @Override + public Characteristic getCharacteristic() + { + return Characteristics.DEFAULT; + } + + @Override + public String toString() + { + String result = this.getClass().getSimpleName() + " = "; + for (byte d : data) + { + result += String.format("0x%02x ", d); + } + return result; + } + + @Override + public boolean isReadable() + { + return false; + } + + @Override + public boolean isWritable() + { + return false; + } +} diff --git a/src/main/java/com/movisens/smartgattlib/attributes/HeartRateMeasurement.java b/src/main/java/com/movisens/smartgattlib/attributes/HeartRateMeasurement.java new file mode 100644 index 0000000..9615329 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/attributes/HeartRateMeasurement.java @@ -0,0 +1,110 @@ +package com.movisens.smartgattlib.attributes; + +import java.util.ArrayList; + +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.GattUtils; +import com.movisens.smartgattlib.helper.AbstractReadAttribute; +import com.movisens.smartgattlib.helper.Characteristic; +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class HeartRateMeasurement extends AbstractReadAttribute +{ + + public static final Characteristic CHARACTERISTIC = Characteristics.HEART_RATE_MEASUREMENT; + + ArrayList rrIntervals = new ArrayList(); + int hrmval = 0; + int eeval = -1; + byte flags; + HeartRateMeasurement.SensorWorn sensorWorn = HeartRateMeasurement.SensorWorn.UNSUPPORTED; + + public enum SensorWorn { + UNSUPPORTED, WORN, NOT_WORN + } + + public HeartRateMeasurement(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + flags = bb.getInt8(); + if (isHeartRateInUINT16()) { + hrmval = bb.getUint16(); + } else { + hrmval = bb.getUint8(); + } + if (isWornStatusPresent()) { + if (isSensorWorn()) { + sensorWorn = HeartRateMeasurement.SensorWorn.WORN; + } else { + sensorWorn = HeartRateMeasurement.SensorWorn.NOT_WORN; + } + } + if (isEePresent()) { + eeval = bb.getUint16(); + } + if (isRrIntPresent()) { + while (bb.hasRemaining()) { + rrIntervals.add(bb.getUint16() / 1024F); + } + } + } + + public boolean isHeartRateInUINT16() { + return (flags & GattUtils.FIRST_BITMASK) != 0; + } + + public boolean isWornStatusPresent() { + return (flags & GattUtils.THIRD_BITMASK) != 0; + } + + public boolean isSensorWorn() { + return (flags & GattUtils.SECOND_BITMASK) != 0; + } + + public boolean isEePresent() { + return (flags & GattUtils.FOURTH_BITMASK) != 0; + } + + public boolean isRrIntPresent() { + return (flags & GattUtils.FIFTH_BITMASK) != 0; + } + + /** + * @return RR-Intervals. Units: seconds + */ + public ArrayList getRrIntervals() { + return rrIntervals; + } + + public int getHr() { + return hrmval; + } + + public String getHrUnit() { + return "bpm"; + } + + /** + * @return Energy Expended, Units: kilo Joules + */ + public int getEe() { + return eeval; + } + + public SensorWorn getSensorWorn() { + return sensorWorn; + } + + @Override + public Characteristic getCharacteristic() + { + return CHARACTERISTIC; + } + + @Override + public String toString() + { + return "Heart Rate Measurement: " + "hr = " + getHr() + " " + getHrUnit() + ", " + "ee = " + getEe() + ", " + "sensorworn = " + getSensorWorn(); + } +} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Age.java b/src/main/java/com/movisens/smartgattlib/characteristics/Age.java deleted file mode 100644 index c6fee4b..0000000 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Age.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.movisens.smartgattlib.characteristics; - -import com.movisens.smartgattlib.GattByteBuffer; -import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; - -public class Age extends AbstractCharacteristic { - - public Age(byte[] bytes) { - super(bytes); - } - - public Age(Short value) { - super(value); - } - - - @Override - protected Short getValueForBytes(byte[] bytes) { - return GattByteBuffer.wrap(bytes).getUint8(); - } - - @Override - protected byte[] getBytesForValue(Short value) { - return GattByteBuffer.allocate(4).putUint8(value).array(); - } - -} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java b/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java deleted file mode 100644 index e773e13..0000000 --- a/src/main/java/com/movisens/smartgattlib/characteristics/BatteryLevel.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.movisens.smartgattlib.characteristics; - -import com.movisens.smartgattlib.GattByteBuffer; -import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; - -public class BatteryLevel extends AbstractReadOnlyCharacteristic { - - public BatteryLevel(byte[] bytes) { - super(bytes); - } - - @Override - protected Short getValueForBytes(byte[] bytes) { - return GattByteBuffer.wrap(bytes).getUint8(); - } - -} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java b/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java deleted file mode 100644 index 7038583..0000000 --- a/src/main/java/com/movisens/smartgattlib/characteristics/BodySensorLocation.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.movisens.smartgattlib.characteristics; - -import com.movisens.smartgattlib.GattByteBuffer; -import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; - -public class BodySensorLocation extends AbstractReadOnlyCharacteristic { - - public BodySensorLocation(byte[] bytes) { - super(bytes); - } - - public enum Location { - Other, Chest, Wrist, Finger, Hand, Ear_Lobe, Foot; - } - - @Override - protected Location getValueForBytes(byte[] bytes) { - Location location = Location.Other; - int loc = GattByteBuffer.wrap(bytes).getUint8(); - - switch (loc) { - case 0: - location = Location.Other; - break; - case 1: - location = Location.Chest; - break; - case 2: - location = Location.Wrist; - break; - case 3: - location = Location.Finger; - break; - case 4: - location = Location.Hand; - break; - case 5: - location = Location.Ear_Lobe; - break; - case 6: - location = Location.Foot; - break; - } - return location; - } - -} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java b/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java deleted file mode 100644 index ccfe5a4..0000000 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Gender.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.movisens.smartgattlib.characteristics; - -import com.movisens.smartgattlib.GattByteBuffer; -import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; - -public class Gender extends AbstractCharacteristic { - public Gender(byte[] bytes) { - super(bytes); - } - - public Gender(Sex value) { - super(value); - } - - public static enum Sex { - MALE((short) 0), FEMALE((short) 1); - - public final short value; - - Sex(short value) { - this.value = value; - } - } - - @Override - protected Sex getValueForBytes(byte[] bytes) { - if (GattByteBuffer.wrap(bytes).getUint8() == 0) { - return Sex.MALE; - } else { - return Sex.FEMALE; - } - } - - @Override - protected byte[] getBytesForValue(Sex value) { - return GattByteBuffer.allocate(4).putUint8(value.value).array(); - } - -} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java b/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java deleted file mode 100644 index a21a84e..0000000 --- a/src/main/java/com/movisens/smartgattlib/characteristics/HeartRateMeasurement.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.movisens.smartgattlib.characteristics; - -import java.util.ArrayList; - -import com.movisens.smartgattlib.GattByteBuffer; -import com.movisens.smartgattlib.GattUtils; -import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; - -public class HeartRateMeasurement extends AbstractReadOnlyCharacteristic { - - ArrayList rrIntervals = new ArrayList(); - int hrmval = 0; - int eeval = -1; - SensorWorn sensorWorn = SensorWorn.UNSUPPORTED; - - public HeartRateMeasurement(byte[] bytes) { - super(bytes); - } - - public enum SensorWorn { - UNSUPPORTED, WORN, NOT_WORN - } - - @Override - protected Integer getValueForBytes(byte[] bytes) { - GattByteBuffer bb = GattByteBuffer.wrap(bytes); - byte flags = bb.getInt8(); - if (isHeartRateInUINT16(flags)) { - hrmval = bb.getUint16(); - } else { - hrmval = bb.getUint8(); - } - if (isWornStatusPresent(flags)) { - if (isSensorWorn(flags)) { - sensorWorn = SensorWorn.WORN; - } else { - sensorWorn = SensorWorn.NOT_WORN; - } - } - if (isEePresent(flags)) { - eeval = bb.getUint16(); - } - if (isRrIntPresent(flags)) { - while (bb.hasRemaining()) { - rrIntervals.add(bb.getUint16() / 1024F); - } - } - return hrmval; - } - - private boolean isHeartRateInUINT16(byte flags) { - return (flags & GattUtils.FIRST_BITMASK) != 0; - } - - private boolean isWornStatusPresent(byte flags) { - return (flags & GattUtils.THIRD_BITMASK) != 0; - } - - private boolean isSensorWorn(byte flags) { - return (flags & GattUtils.SECOND_BITMASK) != 0; - } - - private boolean isEePresent(byte flags) { - return (flags & GattUtils.FOURTH_BITMASK) != 0; - } - - private boolean isRrIntPresent(byte flags) { - return (flags & GattUtils.FIFTH_BITMASK) != 0; - } - - /** - * @return RR-Intervals. Units: seconds - */ - public ArrayList getRrInterval() { - return rrIntervals; - } - - public int getHr() { - return hrmval; - } - - /** - * @return Energy Expended, Units: kilo Joules - */ - public int getEe() { - return eeval; - } - - public SensorWorn getSensorWorn() { - return sensorWorn; - } -} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Height.java b/src/main/java/com/movisens/smartgattlib/characteristics/Height.java deleted file mode 100644 index 3f7f271..0000000 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Height.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.movisens.smartgattlib.characteristics; - -import com.movisens.smartgattlib.GattByteBuffer; -import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; - -public class Height extends AbstractCharacteristic { - - public Height(byte[] bytes) { - super(bytes); - } - - public Height(Integer value) { - super(value); - } - - @Override - protected Integer getValueForBytes(byte[] bytes) { - return GattByteBuffer.wrap(bytes).getUint16(); - } - - @Override - protected byte[] getBytesForValue(Integer value) { - return GattByteBuffer.allocate(4).putUint16(value).array(); - } - -} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java b/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java deleted file mode 100644 index 3d505b3..0000000 --- a/src/main/java/com/movisens/smartgattlib/characteristics/ManufacturerNameString.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.movisens.smartgattlib.characteristics; - -import com.movisens.smartgattlib.GattByteBuffer; -import com.movisens.smartgattlib.characteristics.definition.AbstractReadOnlyCharacteristic; - -public class ManufacturerNameString extends AbstractReadOnlyCharacteristic { - - public ManufacturerNameString(byte[] bytes) { - super(bytes); - } - - @Override - protected String getValueForBytes(byte[] bytes) { - return GattByteBuffer.wrap(bytes).getString(); - } -} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java b/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java deleted file mode 100644 index c5d63f8..0000000 --- a/src/main/java/com/movisens/smartgattlib/characteristics/Weight.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.movisens.smartgattlib.characteristics; - -import com.movisens.smartgattlib.GattByteBuffer; -import com.movisens.smartgattlib.characteristics.definition.AbstractCharacteristic; - -public class Weight extends AbstractCharacteristic { - - public Weight(byte[] bytes) { - super(bytes); - } - - public Weight(Float value) { - super(value); - } - - @Override - protected Float getValueForBytes(byte[] bytes) { - return GattByteBuffer.wrap(bytes).getUint16() / 200f; - } - - @Override - protected byte[] getBytesForValue(Float value) { - float storeWeight = value * 200; - return GattByteBuffer.allocate(4).putUint16(Math.round(storeWeight)).array(); - } - -} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java deleted file mode 100644 index 9a3b685..0000000 --- a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractCharacteristic.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.movisens.smartgattlib.characteristics.definition; - -/** - * Created by Robert Zetzsche on 07.03.2017. - */ - -public abstract class AbstractCharacteristic extends AbstractReadOnlyCharacteristic { - protected byte[] bytes; - - public AbstractCharacteristic(byte[] bytes) { - super(bytes); - this.bytes = bytes; - } - - public AbstractCharacteristic(T value) { - this.value = value; - this.bytes = getBytesForValue(value); - } - - public byte[] getBytes() { - return bytes; - } - - protected abstract T getValueForBytes(byte[] bytes); - - protected abstract byte[] getBytesForValue(T value); -} diff --git a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java b/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java deleted file mode 100644 index 3b0407f..0000000 --- a/src/main/java/com/movisens/smartgattlib/characteristics/definition/AbstractReadOnlyCharacteristic.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.movisens.smartgattlib.characteristics.definition; - -/** - * Created by Robert Zetzsche on 07.03.2017. - */ - -public abstract class AbstractReadOnlyCharacteristic { - protected T value; - - AbstractReadOnlyCharacteristic() { - } - - public AbstractReadOnlyCharacteristic(byte[] bytes) { - this.value = getValueForBytes(bytes); - } - - public T getValue() { - return value; - } - - public boolean isValid() { - return true; - } - - protected abstract T getValueForBytes(byte[] bytes); - - @Override - public String toString() { - return getClass().getSimpleName() + "=" + getValue(); - } -} diff --git a/src/main/java/com/movisens/smartgattlib/declarations/CharacteristicDeclaration.java b/src/main/java/com/movisens/smartgattlib/declarations/CharacteristicDeclaration.java new file mode 100644 index 0000000..18ab24d --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/declarations/CharacteristicDeclaration.java @@ -0,0 +1,89 @@ +package com.movisens.smartgattlib.declarations; + +import java.util.UUID; + +import com.movisens.smartgattlib.helper.GattByteBuffer; +import com.movisens.smartgattlib.helper.UuidObject; + +public class CharacteristicDeclaration +{ + + enum EnumCharacteristicProperties { + + Broadcast(0), Read(1), WriteWithoutResponse(2), Write(3), Notify(4), Indicate(5), AuthenticatedSignedWrites(6), ExtendedProperties(7); + + private final byte value; + + EnumCharacteristicProperties(int bitNumber) + { + this.value = (byte) (((byte) 1) << bitNumber); + } + } + + public static final UUID uuid = UuidObject.stringToUuid("2803"); + + private byte[] data; + + private short characteristicProperties; + + private int characteristicValueHandle; + + private UUID characteristicUuid; + + public CharacteristicDeclaration(byte[] data) + { + this.data = data; + GattByteBuffer bb = GattByteBuffer.wrap(data); + characteristicProperties = bb.getUint8(); + characteristicValueHandle = bb.getUint16(); + characteristicUuid = bb.getUuid(); + } + + public boolean supportsNotify() + { + return (characteristicProperties & EnumCharacteristicProperties.Notify.value) != 0; + } + + public boolean supportsIndicate() + { + return (characteristicProperties & EnumCharacteristicProperties.Indicate.value) != 0; + } + + public byte[] getData() + { + return data; + } + + public short getCharacteristicProperties() + { + return characteristicProperties; + } + + public int getCharacteristicValueHandle() + { + return characteristicValueHandle; + } + + public UUID getCharacteristicUuid() + { + return characteristicUuid; + } + + @Override + public String toString() + { + String string = "CharacteristicDeclaration"; + string += " handle: " + characteristicValueHandle; + string += " properties: "; + for (EnumCharacteristicProperties cp : EnumCharacteristicProperties.values()) + { + if ((characteristicProperties & cp.value) == cp.value) + { + string += cp.name().toUpperCase() + " "; + } + } + string += " uuid: " + characteristicUuid.toString(); + return string; + } + +} diff --git a/src/main/java/com/movisens/smartgattlib/descriptors/ClientCharacteristicConfiguration.java b/src/main/java/com/movisens/smartgattlib/descriptors/ClientCharacteristicConfiguration.java new file mode 100644 index 0000000..af54f8e --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/descriptors/ClientCharacteristicConfiguration.java @@ -0,0 +1,42 @@ +package com.movisens.smartgattlib.descriptors; + +import java.util.UUID; + +import com.movisens.smartgattlib.helper.GattByteBuffer; +import com.movisens.smartgattlib.helper.UuidObject; + +public class ClientCharacteristicConfiguration +{ + + public enum EnumProperties { + + Notifications(0), Indications(1); + + private final byte value; + + EnumProperties(int bitNumber) + { + this.value = (byte) (((byte) 1) << bitNumber); + } + } + + public static final UUID uuid = UuidObject.stringToUuid("2902"); + + private byte[] data; + + private EnumProperties properties; + + public ClientCharacteristicConfiguration(EnumProperties properties) + { + this.properties = properties; + GattByteBuffer bb = GattByteBuffer.allocate(2); + bb.putUint16(this.properties.value); + this.data = bb.array(); + } + + public byte[] getBytes() + { + return data; + } + +} diff --git a/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java new file mode 100644 index 0000000..9393b9b --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java @@ -0,0 +1,19 @@ +package com.movisens.smartgattlib.helper; + +public abstract class AbstractAttribute +{ + + protected byte[] data; + + public byte[] getBytes() + { + return data; + } + + public abstract Characteristic getCharacteristic(); + + public abstract boolean isReadable(); + + public abstract boolean isWritable(); + +} diff --git a/src/main/java/com/movisens/smartgattlib/helper/AbstractReadAttribute.java b/src/main/java/com/movisens/smartgattlib/helper/AbstractReadAttribute.java new file mode 100644 index 0000000..e4aed80 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/helper/AbstractReadAttribute.java @@ -0,0 +1,19 @@ +package com.movisens.smartgattlib.helper; + + +public abstract class AbstractReadAttribute extends AbstractAttribute +{ + + @Override + public boolean isReadable() + { + return true; + } + + @Override + public boolean isWritable() + { + return false; + } + +} diff --git a/src/main/java/com/movisens/smartgattlib/helper/AbstractReadWriteAttribute.java b/src/main/java/com/movisens/smartgattlib/helper/AbstractReadWriteAttribute.java new file mode 100644 index 0000000..25678cc --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/helper/AbstractReadWriteAttribute.java @@ -0,0 +1,18 @@ +package com.movisens.smartgattlib.helper; + + +public abstract class AbstractReadWriteAttribute extends AbstractAttribute +{ + @Override + public boolean isReadable() + { + return true; + } + + @Override + public boolean isWritable() + { + return true; + } + +} diff --git a/src/main/java/com/movisens/smartgattlib/helper/AbstractWriteAttribute.java b/src/main/java/com/movisens/smartgattlib/helper/AbstractWriteAttribute.java new file mode 100644 index 0000000..f800146 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/helper/AbstractWriteAttribute.java @@ -0,0 +1,18 @@ +package com.movisens.smartgattlib.helper; + +public abstract class AbstractWriteAttribute extends AbstractAttribute +{ + + @Override + public boolean isReadable() + { + return false; + } + + @Override + public boolean isWritable() + { + return true; + } + +} diff --git a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java new file mode 100644 index 0000000..ec7c125 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java @@ -0,0 +1,35 @@ +package com.movisens.smartgattlib.helper; + +import com.movisens.smartgattlib.attributes.DefaultAttribute; + +import java.util.UUID; + +public class Characteristic extends UuidObject +{ + + private Class attributeClass; + + public Characteristic(String uuid, String name, Class attributeClass) + { + super(uuid, name); + this.attributeClass = attributeClass; + } + + public AbstractAttribute createAttribute(byte[] data) + { + try + { + return attributeClass.getConstructor(byte[].class).newInstance(data); + } + catch (Throwable e) + { + e.printStackTrace(); + return new DefaultAttribute(data); + } + } + + public Class getAttributeClass() + { + return attributeClass; + } +} diff --git a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java new file mode 100644 index 0000000..eb0f0fe --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java @@ -0,0 +1,261 @@ +package com.movisens.smartgattlib.helper; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Date; +import java.util.UUID; + +public class GattByteBuffer +{ + + public static GattByteBuffer allocate(int i) + { + GattByteBuffer result = new GattByteBuffer(); + result.buffer = ByteBuffer.allocate(i); + result.buffer.order(ByteOrder.LITTLE_ENDIAN); + return result; + } + + public static GattByteBuffer wrap(byte[] byteArray) + { + GattByteBuffer result = new GattByteBuffer(); + result.buffer = ByteBuffer.wrap(byteArray); + result.buffer.order(ByteOrder.LITTLE_ENDIAN); + return result; + } + + private ByteBuffer buffer; + + public byte[] array() + { + return buffer.array(); + } + + public final int capacity() + { + return buffer.capacity(); + } + + public void getInt8(byte[] value, int i, int j) + { + buffer.get(value, i, j); + } + + public void getUint8(short[] value, int offset, int length) + { + for (int i = 0; i < length; i++) + { + value[i + offset] = getUint8(); + } + } + + public Boolean getBoolean() + { + if (buffer.get() == (byte) 0) + { + return false; + } + else + { + return true; + } + } + + public Float getFloat32() + { + return buffer.getFloat(); + } + + public Short getInt16() + { + return buffer.getShort(); + } + + public Integer getInt32() + { + return buffer.getInt(); + } + + public Long getInt64() + { + return buffer.getLong(); + } + + public Byte getInt8() + { + return buffer.get(); + } + + public String getString() + { + String result = ""; + byte c; + + while (((c = buffer.get()) != 0) && buffer.hasRemaining()) + { + result += (char) c; + } + + return result; + } + + public Integer getUint16() + { + int result = buffer.getShort(); + if (result < 0) + { + result += ((int) 1) << 16; + } + return result; + } + + public Long getUint32() + { + long result = buffer.getInt(); + if (result < 0) + { + result += ((long) 1) << 32; + } + return result; + } + + public Short getUint8() + { + short result = buffer.get(); + if (result < 0) + { + result += ((short) 1) << 8; + } + return result; + } + + public GattByteBuffer position(int i) + { + buffer.position(i); + return this; + } + + public GattByteBuffer putUint8(short[] value, int offset, int length) + { + for (int i = 0; i < length; i++) + { + putUint8(value[i + offset]); + } + return this; + } + + public GattByteBuffer putInt8(byte[] value, int i, int j) + { + buffer.put(value, i, j); + return this; + } + + public GattByteBuffer putBoolean(boolean doEnable) + { + if (doEnable) + { + buffer.put((byte) 1); + } + else + { + buffer.put((byte) 0); + } + return this; + } + + public GattByteBuffer putFloat32(Float value) + { + buffer.putFloat(value); + return this; + } + + public GattByteBuffer putInt16(short value) + { + buffer.putShort(value); + return this; + } + + public GattByteBuffer putInt32(int value) + { + buffer.putInt(value); + return this; + } + + public GattByteBuffer putInt64(long value) + { + buffer.putLong(value); + return this; + } + + public GattByteBuffer putInt8(byte value) + { + buffer.put(value); + return this; + } + + public GattByteBuffer putString(String value) + { + buffer.put(value.getBytes()); + buffer.put((byte) 0); + return this; + } + + public GattByteBuffer putUint16(int value) + { + buffer.putShort((short) value); + return this; + } + + public GattByteBuffer putUint32(long value) + { + buffer.putInt((int) value); + return this; + } + + public GattByteBuffer putUint8(short value) + { + buffer.put((byte) value); + return this; + } + + public GattByteBuffer rewind() + { + buffer.rewind(); + return this; + } + + public boolean hasRemaining() + { + return buffer.hasRemaining(); + } + + public void putStime(Date date) + { + putUint32(date.getTime()/1000); + } + + public Date getStime() + { + return new Date(getUint32()*1000); + } + + public UUID getUuid() + { + int length = buffer.remaining(); + byte[] data = new byte[length]; + buffer.get(data); + + String uuidString = ""; + + for (int i = data.length - 1; i >= 0; i--) + { + if (i == 5 || i == 7 || i == 9 || i == 11) + { + uuidString += "-"; + } + uuidString += String.format("%02x", data[i]); + } + + return UuidObject.stringToUuid(uuidString); + } +} \ No newline at end of file diff --git a/src/main/java/com/movisens/smartgattlib/helper/Service.java b/src/main/java/com/movisens/smartgattlib/helper/Service.java new file mode 100644 index 0000000..1fb5096 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/helper/Service.java @@ -0,0 +1,11 @@ +package com.movisens.smartgattlib.helper; + +public class Service extends UuidObject +{ + + public Service(String uuid, String name) + { + super(uuid, name); + } + +} diff --git a/src/main/java/com/movisens/smartgattlib/helper/UuidObject.java b/src/main/java/com/movisens/smartgattlib/helper/UuidObject.java new file mode 100644 index 0000000..815c3ee --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/helper/UuidObject.java @@ -0,0 +1,49 @@ +package com.movisens.smartgattlib.helper; + +import java.util.UUID; + +public class UuidObject +{ + + private static final long leastSigUuidBits = 0x800000805f9b34fbL; + + private UUID uuid; + + private String name; + + public UuidObject(String uuidString, String name) + { + this.uuid = stringToUuid(uuidString); + this.name = name; + } + + public UUID getUuid() + { + return uuid; + } + + public String getName() + { + return name; + } + + public static UUID stringToUuid(String uuidString) + { + UUID uuid; + if (uuidString.length() == 4) + { + /* it is a short form uuid */ + uuid = new UUID((Long.parseLong(uuidString, 16) << 32) | 0x1000, leastSigUuidBits); + } + else + { + uuid = UUID.fromString(uuidString); + } + + return uuid; + } + + public boolean equals(UUID uuid) { + return this.getUuid().equals(uuid); + } +} diff --git a/src/main/java/com/movisens/smartgattlib/helper/UuidObjectMap.java b/src/main/java/com/movisens/smartgattlib/helper/UuidObjectMap.java new file mode 100644 index 0000000..c0d81c2 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/helper/UuidObjectMap.java @@ -0,0 +1,20 @@ +package com.movisens.smartgattlib.helper; + +import java.util.HashMap; +import java.util.UUID; + +public class UuidObjectMap +{ + + private HashMap map = new HashMap(); + + public void put(T uuidObject) + { + map.put(uuidObject.getUuid(), uuidObject); + } + + public T get(UUID uuid) + { + return map.get(uuid); + } +} diff --git a/src/test/java/BatteryLevelTest.java b/src/test/java/BatteryLevelTest.java index 88a82fc..87de558 100644 --- a/src/test/java/BatteryLevelTest.java +++ b/src/test/java/BatteryLevelTest.java @@ -1,6 +1,5 @@ import com.movisens.smartgattlib.GattUtils; -import com.movisens.smartgattlib.characteristics.BatteryLevel; -import com.movisens.smartgattlib.characteristics.HeartRateMeasurement; +import com.movisens.smartgattlib.attributes.BatteryLevel; import org.junit.Test; import static org.junit.Assert.assertTrue; @@ -9,6 +8,6 @@ public class BatteryLevelTest { @Test public void testBatteryLevel() { byte[] testValue = GattUtils.hexStringToByteArray("2C"); BatteryLevel batteryLevel = new BatteryLevel(testValue); - assertTrue("Battery level should be 44", batteryLevel.getValue() == 44); + assertTrue("Battery level should be 44", batteryLevel.getLevel() == 44); } } diff --git a/src/test/java/CyclingSpeedCadenceMeasurementTest.java b/src/test/java/CyclingSpeedCadenceMeasurementTest.java index 21af9ff..221c461 100644 --- a/src/test/java/CyclingSpeedCadenceMeasurementTest.java +++ b/src/test/java/CyclingSpeedCadenceMeasurementTest.java @@ -1,5 +1,5 @@ import com.movisens.smartgattlib.GattUtils; -import com.movisens.smartgattlib.characteristics.CyclingSpeedCadenceMeasurement; +import com.movisens.smartgattlib.attributes.CyclingSpeedCadenceMeasurement; import org.junit.Test; @@ -14,8 +14,8 @@ public void testCCM() { assertTrue("Wheel rev should be present", ccm.isWheelRevPresent()); assertTrue("Crank rev should be present", ccm.isCrankRevPresent()); assertTrue("Cumulative crank revolutions should be 2704", ccm.getCumulativeCrankRevolutions() == 2704); - assertTrue("Last crank event time should be 0", ccm.getLastCrankEventTime() == 40744); + assertTrue("Last crank event time should be 0", ccm.getCrankEventTime() == 40744); assertTrue("Cumulative wheel revolutions should be 1581", ccm.getCumulativeWheelRevolutions() == 1581); - assertTrue("Last wheel even time should be 43427", ccm.getLastWheelEventTime() == 43427); + assertTrue("Last wheel even time should be 43427", ccm.getWheelEventTime() == 43427); } } diff --git a/src/test/java/Example.java b/src/test/java/Example.java index fe4c228..571705f 100644 --- a/src/test/java/Example.java +++ b/src/test/java/Example.java @@ -1,8 +1,8 @@ import java.util.UUID; -import com.movisens.smartgattlib.Characteristic; -import com.movisens.smartgattlib.Service; -import com.movisens.smartgattlib.characteristics.HeartRateMeasurement; +import com.movisens.smartgattlib.*; +import com.movisens.smartgattlib.attributes.*; +import com.movisens.smartgattlib.helper.*; public class Example { @@ -10,27 +10,37 @@ public static void main(String[] args) { // onConnected // TODO: iterate over available services UUID serviceUuid = null;// service.getUuid(); - if (Service.HEART_RATE.equals(serviceUuid)) { + if (Services.HEART_RATE.equals(serviceUuid)) { // TODO: iterate over characteristics UUID characteristicUuid = null;// characteristic.getUuid(); - if (Characteristic.HEART_RATE_MEASUREMENT.equals(characteristicUuid)) { - // TODO: Enable notification - //BluetoothGattDescriptor descriptor = characteristic.getDescriptor(Descriptor.CLIENT_CHARACTERISTIC_CONFIGURATION); - //descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); - //mBluetoothGatt.writeDescriptor(descriptor); + if (Characteristics.HEART_RATE_MEASUREMENT.equals(characteristicUuid)) { + // TODO: Enable notification e.g. for Android API 18: + // BluetoothGattDescriptor descriptor = characteristic.getDescriptor(Descriptor.CLIENT_CHARACTERISTIC_CONFIGURATION); + // descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); + // mBluetoothGatt.writeDescriptor(descriptor); } } else { - System.out.println("Found unused Service: " + Service.lookup(serviceUuid, "unknown")); + System.out.println("Found unused Service: " + Services.lookup(serviceUuid)); } // onCharacteristicChanged - UUID characteristicUuid = null;// characteristic.getUuid(); - if (Characteristic.HEART_RATE_MEASUREMENT.equals(characteristicUuid)) { - byte[] value = null;// characteristic.getValue(); - HeartRateMeasurement hrm = new HeartRateMeasurement(value); - hrm.getHr(); - hrm.getEe(); + UUID uuid = null;// characteristic.getUuid(); + byte[] data = null;// characteristic.getValue(); + + AbstractAttribute a = Characteristics.lookup(uuid).createAttribute(data); + if (a instanceof HeartRateMeasurement) { + HeartRateMeasurement heartRateMeasurement = ((HeartRateMeasurement) a); + heartRateMeasurement.getHr(); + heartRateMeasurement.getEe(); + } else if (a instanceof DefaultAttribute) { + System.err.println("characteristic for " + uuid + " is unknown"); + } else { + System.out.println("unused characteristic " + a.getCharacteristic().getName()); } + + // write Attribute + AbstractAttribute aa = new Weight(12.3); + // TODO: Write aa.getBytes() to aa.getCharacteristic().getUuid(); } } diff --git a/src/test/java/HeartRateMeasurementTest.java b/src/test/java/HeartRateMeasurementTest.java index 21762ff..0b5e587 100644 --- a/src/test/java/HeartRateMeasurementTest.java +++ b/src/test/java/HeartRateMeasurementTest.java @@ -1,5 +1,5 @@ import com.movisens.smartgattlib.GattUtils; -import com.movisens.smartgattlib.characteristics.HeartRateMeasurement; +import com.movisens.smartgattlib.attributes.HeartRateMeasurement; import org.junit.Test; import static org.junit.Assert.*; @@ -10,7 +10,7 @@ public class HeartRateMeasurementTest { assertTrue("HR should be 72", hrm.getHr() == 72); assertTrue("Sensor is worn", hrm.getSensorWorn() == HeartRateMeasurement.SensorWorn.WORN); assertTrue("EE should not be available", hrm.getEe() == -1); - assertTrue("2 RR intervals should be available", hrm.getRrInterval().size() == 2); - assertTrue("RR intervals should be correct", hrm.getRrInterval().get(0) == 0.33203125 && hrm.getRrInterval().get(1) == 0.73046875); + assertTrue("2 RR intervals should be available", hrm.getRrIntervals().size() == 2); + assertTrue("RR intervals should be correct", hrm.getRrIntervals().get(0) == 0.33203125 && hrm.getRrIntervals().get(1) == 0.73046875); } } diff --git a/src/test/java/WeightTest.java b/src/test/java/WeightTest.java index 276cd44..a43b0a3 100644 --- a/src/test/java/WeightTest.java +++ b/src/test/java/WeightTest.java @@ -1,9 +1,11 @@ -import com.movisens.smartgattlib.GattByteBuffer; -import com.movisens.smartgattlib.characteristics.Weight; +import com.movisens.smartgattlib.attributes.Weight; +import com.movisens.smartgattlib.helper.GattByteBuffer; import org.junit.Test; import java.util.Arrays; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** @@ -13,14 +15,14 @@ public class WeightTest { @Test public void testWeight() { Float weightFloat = 12.5f; - byte[] bytes = GattByteBuffer.allocate(4).putUint16(Math.round(weightFloat * 200)).array(); - Weight weight = new Weight(weightFloat); + byte[] bytes = GattByteBuffer.allocate(2).putUint16(Math.round(weightFloat * 200)).array(); + Weight weight = new Weight(weightFloat.doubleValue()); Weight weightBytes = new Weight(bytes); - assertTrue(weight.getValue().equals(weightFloat)); - assertTrue(weightBytes.getValue().equals(weightFloat)); - assertTrue(weightBytes.getValue().equals(weight.getValue())); - assertTrue(Arrays.equals(weight.getBytes(), bytes)); - assertTrue(Arrays.equals(weightBytes.getBytes(), bytes)); - assertTrue(Arrays.equals(weight.getBytes(), weightBytes.getBytes())); + assertEquals(weight.getWeight(), weightFloat, 0); + assertEquals(weightBytes.getWeight(), weightFloat.doubleValue(), 0); + assertEquals(weightBytes.getWeight(), weight.getWeight()); + assertArrayEquals(weight.getBytes(), bytes); + assertArrayEquals(weightBytes.getBytes(), bytes); + assertArrayEquals(weight.getBytes(), weightBytes.getBytes()); } } From daecaa659240cca4440af6ada72969a540778632 Mon Sep 17 00:00:00 2001 From: grossmann Date: Wed, 8 Nov 2017 09:32:46 +0100 Subject: [PATCH 16/60] fixed string representation of attributes --- src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java | 2 +- .../java/com/movisens/smartgattlib/attributes/Appearance.java | 2 +- .../java/com/movisens/smartgattlib/attributes/BatteryLevel.java | 2 +- .../java/com/movisens/smartgattlib/attributes/DateOfBirth.java | 2 +- .../java/com/movisens/smartgattlib/attributes/DeviceName.java | 2 +- .../smartgattlib/attributes/FirmwareRevisionString.java | 2 +- .../main/java/com/movisens/smartgattlib/attributes/Gender.java | 2 +- .../main/java/com/movisens/smartgattlib/attributes/Height.java | 2 +- .../smartgattlib/attributes/ManufacturerNameString.java | 2 +- .../com/movisens/smartgattlib/attributes/ModelNumberString.java | 2 +- .../movisens/smartgattlib/attributes/SerialNumberString.java | 2 +- .../main/java/com/movisens/smartgattlib/attributes/Weight.java | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java index 721c714..3e42f89 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java @@ -46,6 +46,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Age: " + "age = " + getAge() + " " + getAgeUnit(); + return "Age: " + "age = " + getAge(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java index 30c46c9..6633c63 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Appearance: " + "category = " + getCategory() + " " + getCategoryUnit(); + return "Appearance: " + "category = " + getCategory(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java index f3eef83..1f5bc3f 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Battery Level: " + "level = " + getLevel() + " " + getLevelUnit(); + return "Battery Level: " + "level = " + getLevel() + getLevelUnit(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java index ad5b0f9..4a5c83b 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java @@ -74,6 +74,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Date of Birth: " + "year = " + getYear() + " " + getYearUnit() + ", " + "month = " + getMonth() + " " + getMonthUnit() + ", " + "day = " + getDay() + " " + getDayUnit(); + return "Date of Birth: " + "year = " + getYear() + ", " + "month = " + getMonth() + ", " + "day = " + getDay(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java index 54e163d..ac6196b 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Device Name: " + "name = " + getName() + " " + getNameUnit(); + return "Device Name: " + "name = " + getName(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java index b82fca7..d307b44 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Firmware Revision String: " + "firmware_Revision = " + getFirmware_Revision() + " " + getFirmware_RevisionUnit(); + return "Firmware Revision String: " + "firmware_Revision = " + getFirmware_Revision(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java index c0d0ddf..70f4408 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java @@ -46,6 +46,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Gender: " + "gender = " + getGender() + " " + getGenderUnit(); + return "Gender: " + "gender = " + getGender(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java index 5957de3..f695e1f 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java @@ -46,6 +46,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Height: " + "heigt = " + getHeigt() + " " + getHeigtUnit(); + return "Height: " + "heigt = " + getHeigt() + getHeigtUnit(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java index 7ef0dac..f7a1130 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Manufacturer Name String: " + "manufacturer_Name = " + getManufacturer_Name() + " " + getManufacturer_NameUnit(); + return "Manufacturer Name String: " + "manufacturer_Name = " + getManufacturer_Name(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java index 88f864a..463cce7 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Model Number String: " + "model_Number = " + getModel_Number() + " " + getModel_NumberUnit(); + return "Model Number String: " + "model_Number = " + getModel_Number(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java index b532522..6fc04fa 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Serial Number String: " + "serial_Number = " + getSerial_Number() + " " + getSerial_NumberUnit(); + return "Serial Number String: " + "serial_Number = " + getSerial_Number(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java index f1779ac..446de9b 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java @@ -46,6 +46,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Weight: " + "weight = " + getWeight() + " " + getWeightUnit(); + return "Weight: " + "weight = " + getWeight() + getWeightUnit(); } } From 599edd960410f7ba8c576021ab16d703d566c902 Mon Sep 17 00:00:00 2001 From: grossmann Date: Wed, 8 Nov 2017 09:51:31 +0100 Subject: [PATCH 17/60] gradle wrapper update to 4.3 --- .gitignore | 5 +++ build.gradle | 4 ++ gradle/wrapper/gradle-wrapper.jar | Bin 51017 -> 53556 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 52 +++++++++++------------ gradlew.bat | 8 ++-- 6 files changed, 41 insertions(+), 32 deletions(-) diff --git a/.gitignore b/.gitignore index a35cfc3..66e779a 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,8 @@ dist/ *.iml *.ipr *.iws + +# eclipse files +.project +.classpath +.settings/ \ No newline at end of file diff --git a/build.gradle b/build.gradle index 4b5bad4..b7b612a 100644 --- a/build.gradle +++ b/build.gradle @@ -49,3 +49,7 @@ jar { ) } } + +task wrapper(type: Wrapper) { + gradleVersion = '4.3' +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 3d0dee6e8edfecc92e04653ec780de06f7b34f8b..ca78035ef0501d802d4fc55381ef2d5c3ce0ec6e 100644 GIT binary patch delta 29037 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" @@ -30,6 +48,7 @@ die ( ) { cygwin=false msys=false darwin=false +nonstop=false case "`uname`" in CYGWIN* ) cygwin=true @@ -40,31 +59,11 @@ case "`uname`" in MINGW* ) msys=true ;; + NONSTOP* ) + nonstop=true + ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. @@ -90,7 +89,7 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then @@ -114,6 +113,7 @@ fi if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` diff --git a/gradlew.bat b/gradlew.bat index 8a0b282..832fdb6 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -8,14 +8,14 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome @@ -46,7 +46,7 @@ echo location of your Java installation. goto fail :init -@rem Get command-line arguments, handling Windowz variants +@rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args if "%@eval[2+2]" == "4" goto 4NT_args From eb8b9596cef6173b1a2be295f987cf7aed8db116 Mon Sep 17 00:00:00 2001 From: grossmann Date: Wed, 8 Nov 2017 09:58:46 +0100 Subject: [PATCH 18/60] version update --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b7b612a..08e6fe3 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.0.0' +version = '3.0.1' repositories { mavenCentral() From 58d388b398172072177026bb96dc5e7bb8d3d1d0 Mon Sep 17 00:00:00 2001 From: grossmann Date: Fri, 10 Nov 2017 08:56:33 +0100 Subject: [PATCH 19/60] added millisecond time --- build.gradle | 2 +- .../com/movisens/smartgattlib/helper/GattByteBuffer.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 08e6fe3..38d952a 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.0.1' +version = '3.0.2' repositories { mavenCentral() diff --git a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java index eb0f0fe..4407fad 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java +++ b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java @@ -239,6 +239,11 @@ public Date getStime() return new Date(getUint32()*1000); } + public Date getMstime() + { + return new Date(getInt64()); + } + public UUID getUuid() { int length = buffer.remaining(); From 21efe4808ddd05688cb719965f430c05211689f2 Mon Sep 17 00:00:00 2001 From: grossmann Date: Mon, 13 Nov 2017 12:49:57 +0100 Subject: [PATCH 20/60] fixed default value for BodySensorLocation --- .../movisens/smartgattlib/attributes/BodySensorLocation.java | 2 +- .../java/com/movisens/smartgattlib/helper/Characteristic.java | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/com/movisens/smartgattlib/attributes/BodySensorLocation.java b/src/main/java/com/movisens/smartgattlib/attributes/BodySensorLocation.java index 980de53..3b7b70a 100644 --- a/src/main/java/com/movisens/smartgattlib/attributes/BodySensorLocation.java +++ b/src/main/java/com/movisens/smartgattlib/attributes/BodySensorLocation.java @@ -28,7 +28,7 @@ public String getLocationUnit() public BodySensorLocation(byte[] data) { this.data = data; - Location location = Location.Other; + location = Location.Other; int loc = GattByteBuffer.wrap(data).getUint8(); switch (loc) { diff --git a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java index ec7c125..5144d96 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java +++ b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java @@ -2,8 +2,6 @@ import com.movisens.smartgattlib.attributes.DefaultAttribute; -import java.util.UUID; - public class Characteristic extends UuidObject { From e4d85e5670966a51bf5f7283a1c3828ad3448912 Mon Sep 17 00:00:00 2001 From: grossmann Date: Tue, 21 Nov 2017 14:19:19 +0100 Subject: [PATCH 21/60] version update --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 38d952a..5345322 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.0.2' +version = '3.0.3' repositories { mavenCentral() From 47fc98387b1421aad35ec92e6e78acd6ce837b66 Mon Sep 17 00:00:00 2001 From: grossmann Date: Wed, 22 Nov 2017 11:08:07 +0100 Subject: [PATCH 22/60] bugfix: do not skip last character in readString --- .../java/com/movisens/smartgattlib/helper/GattByteBuffer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java index 4407fad..a96472d 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java +++ b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java @@ -91,7 +91,7 @@ public String getString() String result = ""; byte c; - while (((c = buffer.get()) != 0) && buffer.hasRemaining()) + while (buffer.hasRemaining() && ((c = buffer.get()) != 0)) { result += (char) c; } From e7d5452e517c66516996b7998bbdd5e4882f6dff Mon Sep 17 00:00:00 2001 From: grossmann Date: Wed, 22 Nov 2017 11:09:00 +0100 Subject: [PATCH 23/60] changed format of toString in attributes --- .../main/java/com/movisens/smartgattlib/attributes/Age.java | 2 +- .../com/movisens/smartgattlib/attributes/Appearance.java | 2 +- .../com/movisens/smartgattlib/attributes/BatteryLevel.java | 2 +- .../com/movisens/smartgattlib/attributes/DateOfBirth.java | 2 +- .../com/movisens/smartgattlib/attributes/DeviceName.java | 2 +- .../smartgattlib/attributes/FirmwareRevisionString.java | 2 +- .../java/com/movisens/smartgattlib/attributes/Gender.java | 2 +- .../java/com/movisens/smartgattlib/attributes/Height.java | 2 +- .../smartgattlib/attributes/ManufacturerNameString.java | 2 +- .../movisens/smartgattlib/attributes/ModelNumberString.java | 2 +- .../smartgattlib/attributes/SerialNumberString.java | 2 +- .../java/com/movisens/smartgattlib/attributes/Weight.java | 2 +- .../com/movisens/smartgattlib/helper/Characteristic.java | 6 ++++++ 13 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java index 3e42f89..545e2df 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java @@ -46,6 +46,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Age: " + "age = " + getAge(); + return getAge().toString(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java index 6633c63..4cdd06c 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Appearance: " + "category = " + getCategory(); + return getCategory().toString(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java index 1f5bc3f..6b5e3c5 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Battery Level: " + "level = " + getLevel() + getLevelUnit(); + return getLevel().toString() + getLevelUnit(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java index 4a5c83b..7719a60 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java @@ -74,6 +74,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Date of Birth: " + "year = " + getYear() + ", " + "month = " + getMonth() + ", " + "day = " + getDay(); + return "year = " + getYear() + ", " + "month = " + getMonth() + ", " + "day = " + getDay(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java index ac6196b..aa42156 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Device Name: " + "name = " + getName(); + return getName().toString(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java index d307b44..ae6bcb3 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Firmware Revision String: " + "firmware_Revision = " + getFirmware_Revision(); + return getFirmware_Revision().toString(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java index 70f4408..3cb185e 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java @@ -46,6 +46,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Gender: " + "gender = " + getGender(); + return getGender().toString(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java index f695e1f..24dacec 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java @@ -46,6 +46,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Height: " + "heigt = " + getHeigt() + getHeigtUnit(); + return getHeigt().toString() + getHeigtUnit(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java index f7a1130..d074b3b 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Manufacturer Name String: " + "manufacturer_Name = " + getManufacturer_Name(); + return getManufacturer_Name().toString(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java index 463cce7..28684ca 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Model Number String: " + "model_Number = " + getModel_Number(); + return getModel_Number().toString(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java index 6fc04fa..ca4e78c 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java @@ -38,6 +38,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Serial Number String: " + "serial_Number = " + getSerial_Number(); + return getSerial_Number().toString(); } } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java index 446de9b..8faaa18 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java @@ -46,6 +46,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "Weight: " + "weight = " + getWeight() + getWeightUnit(); + return getWeight().toString() + getWeightUnit(); } } diff --git a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java index 5144d96..bcfa7f1 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java +++ b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java @@ -30,4 +30,10 @@ public Class getAttributeClass() { return attributeClass; } + + @Override + public String toString() + { + return getName(); + } } From c73a78e00cb6b7a20538f1faa10f1c0a6c4b62f0 Mon Sep 17 00:00:00 2001 From: grossmann Date: Wed, 22 Nov 2017 11:09:54 +0100 Subject: [PATCH 24/60] version update --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5345322..d21f921 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.0.3' +version = '3.0.4' repositories { mavenCentral() From c8d9c2d52aab6a24abdf1c0830ebd7ea63dde7ba Mon Sep 17 00:00:00 2001 From: grossmann Date: Thu, 23 Nov 2017 11:46:24 +0100 Subject: [PATCH 25/60] added range checks to height and wieght --- .../movisens/smartgattlib/Characteristics.java | 17 ++++++++++++++--- .../smartgattlib/attributes/Height.java | 8 ++++++++ .../smartgattlib/attributes/Weight.java | 8 ++++++++ .../smartgattlib/helper/AbstractAttribute.java | 10 ++++++++-- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java index 32bf438..0417270 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java +++ b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java @@ -2,7 +2,20 @@ import java.util.UUID; -import com.movisens.smartgattlib.attributes.*; +import com.movisens.smartgattlib.attributes.DefaultAttribute; +import com.movisens.smartgattlib.attributes.BatteryLevel; +import com.movisens.smartgattlib.attributes.ModelNumberString; +import com.movisens.smartgattlib.attributes.FirmwareRevisionString; +import com.movisens.smartgattlib.attributes.Gender; +import com.movisens.smartgattlib.attributes.DateOfBirth; +import com.movisens.smartgattlib.attributes.HeartRateMeasurement; +import com.movisens.smartgattlib.attributes.Height; +import com.movisens.smartgattlib.attributes.Appearance; +import com.movisens.smartgattlib.attributes.ManufacturerNameString; +import com.movisens.smartgattlib.attributes.DeviceName; +import com.movisens.smartgattlib.attributes.Age; +import com.movisens.smartgattlib.attributes.Weight; +import com.movisens.smartgattlib.attributes.SerialNumberString; import com.movisens.smartgattlib.helper.Characteristic; import com.movisens.smartgattlib.helper.UuidObjectMap; @@ -154,7 +167,6 @@ public class Characteristics public static final Characteristic PRESSURE = new Characteristic("2a6d", "Pressure", DefaultAttribute.class); public static final Characteristic PROTOCOL_MODE = new Characteristic("2a4e", "Protocol Mode", DefaultAttribute.class); public static final Characteristic PULSE_OXIMETRY_CONTROL_POINT = new Characteristic("2a62", "Pulse Oximetry Control Point", DefaultAttribute.class); - public static final Characteristic PULSE_OXIMETRY_PULSATILE_EVENT = new Characteristic("2a60", "Pulse Oximetry Pulsatile Event Characteristic", DefaultAttribute.class); public static final Characteristic RAINFALL = new Characteristic("2a78", "Rainfall", DefaultAttribute.class); public static final Characteristic RECONNECTION_ADDRESS = new Characteristic("2a03", "Reconnection Address", DefaultAttribute.class); public static final Characteristic RECORD_ACCESS_CONTROL_POINT = new Characteristic("2a52", "Record Access Control Point", DefaultAttribute.class); @@ -372,7 +384,6 @@ public class Characteristics characteristics.put(PRESSURE); characteristics.put(PROTOCOL_MODE); characteristics.put(PULSE_OXIMETRY_CONTROL_POINT); - characteristics.put(PULSE_OXIMETRY_PULSATILE_EVENT); characteristics.put(RAINFALL); characteristics.put(RECONNECTION_ADDRESS); characteristics.put(RECORD_ACCESS_CONTROL_POINT); diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java index 24dacec..b39e176 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java @@ -24,6 +24,14 @@ public String getHeigtUnit() public Height(Double heigt) { + if(heigt<0.0) + { + throw new RuntimeException("heigt out of range! Min value is 0.0"); + } + if(heigt>3.0) + { + throw new RuntimeException("heigt out of range! Max value is 3.0"); + } this.heigt = heigt; GattByteBuffer bb = GattByteBuffer.allocate(2); bb.putUint16(new Double(heigt / 0.01).intValue()); diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java index 8faaa18..949b6d7 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java @@ -24,6 +24,14 @@ public String getWeightUnit() public Weight(Double weight) { + if(weight<0.0) + { + throw new RuntimeException("weight out of range! Min value is 0.0"); + } + if(weight>327.68) + { + throw new RuntimeException("weight out of range! Max value is 327.68"); + } this.weight = weight; GattByteBuffer bb = GattByteBuffer.allocate(2); bb.putUint16(new Double(weight / 0.005).intValue()); diff --git a/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java index 9393b9b..c5e7e23 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java +++ b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java @@ -12,8 +12,14 @@ public byte[] getBytes() public abstract Characteristic getCharacteristic(); - public abstract boolean isReadable(); + public boolean isReadable() + { + return false; + } - public abstract boolean isWritable(); + public boolean isWritable() + { + return false; + } } From eb35be27548418f77527707c380d1aa558096e16 Mon Sep 17 00:00:00 2001 From: grossmann Date: Thu, 23 Nov 2017 11:58:59 +0100 Subject: [PATCH 26/60] version update --- build.gradle | 2 +- .../main/java/com/movisens/smartgattlib/Characteristics.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d21f921..31c19f3 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.0.4' +version = '3.0.5' repositories { mavenCentral() diff --git a/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java index 0417270..7ad535f 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java +++ b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java @@ -4,6 +4,8 @@ import com.movisens.smartgattlib.attributes.DefaultAttribute; import com.movisens.smartgattlib.attributes.BatteryLevel; +import com.movisens.smartgattlib.attributes.BodySensorLocation; +import com.movisens.smartgattlib.attributes.CyclingSpeedCadenceMeasurement; import com.movisens.smartgattlib.attributes.ModelNumberString; import com.movisens.smartgattlib.attributes.FirmwareRevisionString; import com.movisens.smartgattlib.attributes.Gender; From 9c66e36c455a8a4d26d68c5647944231d1994c18 Mon Sep 17 00:00:00 2001 From: grossmann Date: Wed, 7 Mar 2018 17:05:21 +0100 Subject: [PATCH 27/60] Characteristic uses generics --- build.gradle | 2 +- .../smartgattlib/Characteristics.java | 447 +++++++++--------- .../movisens/smartgattlib/attributes/Age.java | 4 +- .../smartgattlib/attributes/Appearance.java | 4 +- .../smartgattlib/attributes/BatteryLevel.java | 4 +- .../smartgattlib/attributes/DateOfBirth.java | 4 +- .../smartgattlib/attributes/DeviceName.java | 4 +- .../attributes/FirmwareRevisionString.java | 4 +- .../smartgattlib/attributes/Gender.java | 4 +- .../smartgattlib/attributes/Height.java | 4 +- .../attributes/ManufacturerNameString.java | 4 +- .../attributes/ModelNumberString.java | 4 +- .../attributes/SerialNumberString.java | 4 +- .../smartgattlib/attributes/Weight.java | 4 +- .../attributes/BodySensorLocation.java | 4 +- .../helper/AbstractAttribute.java | 2 +- .../smartgattlib/helper/Characteristic.java | 8 +- 17 files changed, 256 insertions(+), 255 deletions(-) diff --git a/build.gradle b/build.gradle index 31c19f3..120bbec 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.0.5' +version = '3.0.6' repositories { mavenCentral() diff --git a/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java index 7ad535f..b35217f 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java +++ b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java @@ -2,243 +2,244 @@ import java.util.UUID; -import com.movisens.smartgattlib.attributes.DefaultAttribute; +import com.movisens.smartgattlib.attributes.Age; +import com.movisens.smartgattlib.attributes.Appearance; import com.movisens.smartgattlib.attributes.BatteryLevel; import com.movisens.smartgattlib.attributes.BodySensorLocation; import com.movisens.smartgattlib.attributes.CyclingSpeedCadenceMeasurement; -import com.movisens.smartgattlib.attributes.ModelNumberString; +import com.movisens.smartgattlib.attributes.DateOfBirth; +import com.movisens.smartgattlib.attributes.DefaultAttribute; +import com.movisens.smartgattlib.attributes.DeviceName; import com.movisens.smartgattlib.attributes.FirmwareRevisionString; import com.movisens.smartgattlib.attributes.Gender; -import com.movisens.smartgattlib.attributes.DateOfBirth; import com.movisens.smartgattlib.attributes.HeartRateMeasurement; import com.movisens.smartgattlib.attributes.Height; -import com.movisens.smartgattlib.attributes.Appearance; import com.movisens.smartgattlib.attributes.ManufacturerNameString; -import com.movisens.smartgattlib.attributes.DeviceName; -import com.movisens.smartgattlib.attributes.Age; -import com.movisens.smartgattlib.attributes.Weight; +import com.movisens.smartgattlib.attributes.ModelNumberString; import com.movisens.smartgattlib.attributes.SerialNumberString; +import com.movisens.smartgattlib.attributes.Weight; +import com.movisens.smartgattlib.helper.AbstractAttribute; import com.movisens.smartgattlib.helper.Characteristic; import com.movisens.smartgattlib.helper.UuidObjectMap; public class Characteristics { - public static final Characteristic DEFAULT = new Characteristic("0000", "Default Characteristic", DefaultAttribute.class); - public static final Characteristic LONGITUDE = new Characteristic("2aaf", "Longitude", DefaultAttribute.class); - public static final Characteristic MAGNETIC_FLUX_DENSITY_2D = new Characteristic("2aa0", "Magnetic Flux Density - 2D", DefaultAttribute.class); - public static final Characteristic MAGNETIC_FLUX_DENSITY_3D = new Characteristic("2aa1", "Magnetic Flux Density - 3D", DefaultAttribute.class); - public static final Characteristic AEROBIC_HEART_RATE_LOWER_LIMIT = new Characteristic("2a7e", "Aerobic Heart Rate Lower Limit", DefaultAttribute.class); - public static final Characteristic AEROBIC_HEART_RATE_UPPER_LIMIT = new Characteristic("2a84", "Aerobic Heart Rate Upper Limit", DefaultAttribute.class); - public static final Characteristic AEROBIC_THRESHOLD = new Characteristic("2a7f", "Aerobic Threshold", DefaultAttribute.class); - public static final Characteristic AGE = new Characteristic("2a80", "Age", Age.class); - public static final Characteristic AGGREGATE = new Characteristic("2a5a", "Aggregate", DefaultAttribute.class); - public static final Characteristic ALERT_CATEGORY_ID = new Characteristic("2a43", "Alert Category ID", DefaultAttribute.class); - public static final Characteristic ALERT_CATEGORY_ID_BIT_MASK = new Characteristic("2a42", "Alert Category ID Bit Mask", DefaultAttribute.class); - public static final Characteristic ALERT_LEVEL = new Characteristic("2a06", "Alert Level", DefaultAttribute.class); - public static final Characteristic ALERT_NOTIFICATION_CONTROL_POINT = new Characteristic("2a44", "Alert Notification Control Point", DefaultAttribute.class); - public static final Characteristic ALERT_STATUS = new Characteristic("2a3f", "Alert Status", DefaultAttribute.class); - public static final Characteristic ALTITUDE = new Characteristic("2ab3", "Altitude", DefaultAttribute.class); - public static final Characteristic ANAEROBIC_HEART_RATE_LOWER_LIMIT = new Characteristic("2a81", "Anaerobic Heart Rate Lower Limit", DefaultAttribute.class); - public static final Characteristic ANAEROBIC_HEART_RATE_UPPER_LIMIT = new Characteristic("2a82", "Anaerobic Heart Rate Upper Limit", DefaultAttribute.class); - public static final Characteristic ANAEROBIC_THRESHOLD = new Characteristic("2a83", "Anaerobic Threshold", DefaultAttribute.class); - public static final Characteristic ANALOG = new Characteristic("2a58", "Analog", DefaultAttribute.class); - public static final Characteristic ANALOG_OUTPUT = new Characteristic("2a59", "Analog Output", DefaultAttribute.class); - public static final Characteristic APPARENT_WIND_DIRECTION = new Characteristic("2a73", "Apparent Wind Direction", DefaultAttribute.class); - public static final Characteristic APPARENT_WIND_SPEED = new Characteristic("2a72", "Apparent Wind Speed", DefaultAttribute.class); - public static final Characteristic APPEARANCE = new Characteristic("2a01", "Appearance", Appearance.class); - public static final Characteristic BAROMETRIC_PRESSURE_TREND = new Characteristic("2aa3", "Barometric Pressure Trend", DefaultAttribute.class); - public static final Characteristic BATTERY_LEVEL = new Characteristic("2a19", "Battery Level", BatteryLevel.class); - public static final Characteristic BATTERY_LEVEL_STATE = new Characteristic("2a1b", "Battery Level State", DefaultAttribute.class); - public static final Characteristic BATTERY_POWER_STATE = new Characteristic("2a1a", "Battery Power State", DefaultAttribute.class); - public static final Characteristic BLOOD_PRESSURE_FEATURE = new Characteristic("2a49", "Blood Pressure Feature", DefaultAttribute.class); - public static final Characteristic BLOOD_PRESSURE_MEASUREMENT = new Characteristic("2a35", "Blood Pressure Measurement", DefaultAttribute.class); - public static final Characteristic BODY_COMPOSITION_FEATURE = new Characteristic("2a9b", "Body Composition Feature", DefaultAttribute.class); - public static final Characteristic BODY_COMPOSITION_MEASUREMENT = new Characteristic("2a9c", "Body Composition Measurement", DefaultAttribute.class); - public static final Characteristic BODY_SENSOR_LOCATION = new Characteristic("2a38", "Body Sensor Location", BodySensorLocation.class); - public static final Characteristic BOND_MANAGEMENT_CONTROL_POINT = new Characteristic("2aa4", "Bond Management Control Point", DefaultAttribute.class); - public static final Characteristic BOND_MANAGEMENT_FEATURE = new Characteristic("2aa5", "Bond Management Features", DefaultAttribute.class); - public static final Characteristic BOOT_KEYBOARD_INPUT_REPORT = new Characteristic("2a22", "Boot Keyboard Input Report", DefaultAttribute.class); - public static final Characteristic BOOT_KEYBOARD_OUTPUT_REPORT = new Characteristic("2a32", "Boot Keyboard Output Report", DefaultAttribute.class); - public static final Characteristic BOOT_MOUSE_INPUT_REPORT = new Characteristic("2a33", "Boot Mouse Input Report", DefaultAttribute.class); - public static final Characteristic CGM_FEATURE = new Characteristic("2aa8", "CGM Feature", DefaultAttribute.class); - public static final Characteristic CGM_MEASUREMENT = new Characteristic("2aa7", "CGM Measurement", DefaultAttribute.class); - public static final Characteristic CGM_SESSION_RUN_TIME = new Characteristic("2aab", "CGM Session Run Time", DefaultAttribute.class); - public static final Characteristic CGM_SESSION_START_TIME = new Characteristic("2aaa", "CGM Session Start Time", DefaultAttribute.class); - public static final Characteristic CGM_SPECIFIC_OPS_CONTROL_POINT = new Characteristic("2aac", "CGM Specific Ops Control Point", DefaultAttribute.class); - public static final Characteristic CGM_STATUS = new Characteristic("2aa9", "CGM Status", DefaultAttribute.class); - public static final Characteristic CROSS_TRAINER_DATA = new Characteristic("2ace", "Cross Trainer Data", DefaultAttribute.class); - public static final Characteristic CSC_FEATURE = new Characteristic("2a5c", "CSC Feature", DefaultAttribute.class); - public static final Characteristic CSC_MEASUREMENT = new Characteristic("2a5b", "CSC Measurement", CyclingSpeedCadenceMeasurement.class); - public static final Characteristic CURRENT_TIME = new Characteristic("2a2b", "Current Time", DefaultAttribute.class); - public static final Characteristic CYCLING_POWER_CONTROL_POINT = new Characteristic("2a66", "Cycling Power Control Point", DefaultAttribute.class); - public static final Characteristic CYCLING_POWER_FEATURE = new Characteristic("2a65", "Cycling Power Feature", DefaultAttribute.class); - public static final Characteristic CYCLING_POWER_MEASUREMENT = new Characteristic("2a63", "Cycling Power Measurement", DefaultAttribute.class); - public static final Characteristic CYCLING_POWER_VECTOR = new Characteristic("2a64", "Cycling Power Vector", DefaultAttribute.class); - public static final Characteristic DATABASE_CHANGE_INCREMENT = new Characteristic("2a99", "Database Change Increment", DefaultAttribute.class); - public static final Characteristic DATE_OF_BIRTH = new Characteristic("2a85", "Date of Birth", DateOfBirth.class); - public static final Characteristic DATE_OF_THRESHOLD_ASSESSMENT = new Characteristic("2a86", "Date of Threshold Assessment", DefaultAttribute.class); - public static final Characteristic DATE_TIME = new Characteristic("2a08", "Date Time", DefaultAttribute.class); - public static final Characteristic DAY_DATE_TIME = new Characteristic("2a0a", "Day Date Time", DefaultAttribute.class); - public static final Characteristic DAY_OF_WEEK = new Characteristic("2a09", "Day of Week", DefaultAttribute.class); - public static final Characteristic DESCRIPTOR_VALUE_CHANGED = new Characteristic("2a7d", "Descriptor Value Changed", DefaultAttribute.class); - public static final Characteristic DEVICE_NAME = new Characteristic("2a00", "Device Name", DeviceName.class); - public static final Characteristic DEW_POINT = new Characteristic("2a7b", "Dew Point", DefaultAttribute.class); - public static final Characteristic DIGITAL = new Characteristic("2a56", "Digital", DefaultAttribute.class); - public static final Characteristic DIGITAL_OUTPUT = new Characteristic("2a57", "Digital Output", DefaultAttribute.class); - public static final Characteristic DST_OFFSET = new Characteristic("2a0d", "DST Offset", DefaultAttribute.class); - public static final Characteristic ELEVATION = new Characteristic("2a6c", "Elevation", DefaultAttribute.class); - public static final Characteristic EMAIL_ADDRESS = new Characteristic("2a87", "Email Address", DefaultAttribute.class); - public static final Characteristic EXACT_TIME_100 = new Characteristic("2a0b", "Exact Time 100", DefaultAttribute.class); - public static final Characteristic EXACT_TIME_256 = new Characteristic("2a0c", "Exact Time 256", DefaultAttribute.class); - public static final Characteristic FAT_BURN_HEART_RATE_LOWER_LIMIT = new Characteristic("2a88", "Fat Burn Heart Rate Lower Limit", DefaultAttribute.class); - public static final Characteristic FAT_BURN_HEART_RATE_UPPER_LIMIT = new Characteristic("2a89", "Fat Burn Heart Rate Upper Limit", DefaultAttribute.class); - public static final Characteristic FIRMWARE_REVISION_STRING = new Characteristic("2a26", "Firmware Revision String", FirmwareRevisionString.class); - public static final Characteristic FIRST_NAME = new Characteristic("2a8a", "First Name", DefaultAttribute.class); - public static final Characteristic FITNESS_MACHINE_CONTROL_POINT = new Characteristic("2ad9", "Fitness Machine Control Point", DefaultAttribute.class); - public static final Characteristic FITNESS_MACHINE_FEATURE = new Characteristic("2acc", "Fitness Machine Feature", DefaultAttribute.class); - public static final Characteristic FITNESS_MACHINE_STATUS = new Characteristic("2ada", "Fitness Machine Status", DefaultAttribute.class); - public static final Characteristic FIVE_ZONE_HEART_RATE_LIMITS = new Characteristic("2a8b", "Five Zone Heart Rate Limits", DefaultAttribute.class); - public static final Characteristic FLOOR_NUMBER = new Characteristic("2ab2", "Floor Number", DefaultAttribute.class); - public static final Characteristic GENDER = new Characteristic("2a8c", "Gender", Gender.class); - public static final Characteristic GLUCOSE_FEATURE = new Characteristic("2a51", "Glucose Feature", DefaultAttribute.class); - public static final Characteristic GLUCOSE_MEASUREMENT = new Characteristic("2a18", "Glucose Measurement", DefaultAttribute.class); - public static final Characteristic GLUCOSE_MEASUREMENT_CONTEXT = new Characteristic("2a34", "Glucose Measurement Context", DefaultAttribute.class); - public static final Characteristic GUST_FACTOR = new Characteristic("2a74", "Gust Factor", DefaultAttribute.class); - public static final Characteristic HARDWARE_REVISION_STRING = new Characteristic("2a27", "Hardware Revision String", DefaultAttribute.class); - public static final Characteristic HEART_RATE_CONTROL_POINT = new Characteristic("2a39", "Heart Rate Control Point", DefaultAttribute.class); - public static final Characteristic HEART_RATE_MAX = new Characteristic("2a8d", "Heart Rate Max", DefaultAttribute.class); - public static final Characteristic HEART_RATE_MEASUREMENT = new Characteristic("2a37", "Heart Rate Measurement", HeartRateMeasurement.class); - public static final Characteristic HEAT_INDEX = new Characteristic("2a7a", "Heat Index", DefaultAttribute.class); - public static final Characteristic HEIGHT = new Characteristic("2a8e", "Height", Height.class); - public static final Characteristic HID_CONTROL_POINT = new Characteristic("2a4c", "HID Control Point", DefaultAttribute.class); - public static final Characteristic HID_INFORMATION = new Characteristic("2a4a", "HID Information", DefaultAttribute.class); - public static final Characteristic HIP_CIRCUMFERENCE = new Characteristic("2a8f", "Hip Circumference", DefaultAttribute.class); - public static final Characteristic HTTP_CONTROL_POINT = new Characteristic("2aba", "HTTP Control Point", DefaultAttribute.class); - public static final Characteristic HTTP_ENTITY_BODY = new Characteristic("2ab9", "HTTP Entity Body", DefaultAttribute.class); - public static final Characteristic HTTP_HEADERS = new Characteristic("2ab7", "HTTP Headers", DefaultAttribute.class); - public static final Characteristic HTTP_STATUS_CODE = new Characteristic("2ab8", "HTTP Status Code", DefaultAttribute.class); - public static final Characteristic HTTPS_SECURITY = new Characteristic("2abb", "HTTPS Security", DefaultAttribute.class); - public static final Characteristic HUMIDITY = new Characteristic("2a6f", "Humidity", DefaultAttribute.class); - public static final Characteristic IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST = new Characteristic("2a2a", "IEEE 11073-20601 Regulatory Certification Data List", DefaultAttribute.class); - public static final Characteristic INDOOR_BIKE_DATA = new Characteristic("2ad2", "Indoor Bike Data", DefaultAttribute.class); - public static final Characteristic INDOOR_POSITIONING_CONFIGURATION = new Characteristic("2aad", "Indoor Positioning Configuration", DefaultAttribute.class); - public static final Characteristic INTERMEDIATE_CUFF_PRESSURE = new Characteristic("2a36", "Intermediate Cuff Pressure", DefaultAttribute.class); - public static final Characteristic INTERMEDIATE_TEMPERATURE = new Characteristic("2a1e", "Intermediate Temperature", DefaultAttribute.class); - public static final Characteristic IRRADIANCE = new Characteristic("2a77", "Irradiance", DefaultAttribute.class); - public static final Characteristic LANGUAGE = new Characteristic("2aa2", "Language", DefaultAttribute.class); - public static final Characteristic LAST_NAME = new Characteristic("2a90", "Last Name", DefaultAttribute.class); - public static final Characteristic LATITUDE = new Characteristic("2aae", "Latitude", DefaultAttribute.class); - public static final Characteristic LN_CONTROL_POINT = new Characteristic("2a6b", "LN Control Point", DefaultAttribute.class); - public static final Characteristic LN_FEATURE = new Characteristic("2a6a", "LN Feature", DefaultAttribute.class); - public static final Characteristic LOCAL_EAST_COORDINATE = new Characteristic("2ab1", "Local East Coordinate", DefaultAttribute.class); - public static final Characteristic LOCAL_NORTH_COORDINATE = new Characteristic("2ab0", "Local North Coordinate", DefaultAttribute.class); - public static final Characteristic LOCAL_TIME_INFORMATION = new Characteristic("2a0f", "Local Time Information", DefaultAttribute.class); - public static final Characteristic LOCATION_AND_SPEED = new Characteristic("2a67", "Location and Speed Characteristic", DefaultAttribute.class); - public static final Characteristic LOCATION_NAME = new Characteristic("2ab5", "Location Name", DefaultAttribute.class); - public static final Characteristic MAGNETIC_DECLINATION = new Characteristic("2a2c", "Magnetic Declination", DefaultAttribute.class); - public static final Characteristic MANUFACTURER_NAME_STRING = new Characteristic("2a29", "Manufacturer Name String", ManufacturerNameString.class); - public static final Characteristic MAXIMUM_RECOMMENDED_HEART_RATE = new Characteristic("2a91", "Maximum Recommended Heart Rate", DefaultAttribute.class); - public static final Characteristic MEASUREMENT_INTERVAL = new Characteristic("2a21", "Measurement Interval", DefaultAttribute.class); - public static final Characteristic MODEL_NUMBER_STRING = new Characteristic("2a24", "Model Number String", ModelNumberString.class); - public static final Characteristic NAVIGATION = new Characteristic("2a68", "Navigation", DefaultAttribute.class); - public static final Characteristic NETWORK_AVAILABILITY = new Characteristic("2a3e", "Network Availability", DefaultAttribute.class); - public static final Characteristic NEW_ALERT = new Characteristic("2a46", "New Alert", DefaultAttribute.class); - public static final Characteristic OBJECT_ACTION_CONTROL_POINT = new Characteristic("2ac5", "Object Action Control Point", DefaultAttribute.class); - public static final Characteristic OBJECT_CHANGED = new Characteristic("2ac8", "Object Changed", DefaultAttribute.class); - public static final Characteristic OBJECT_FIRST_CREATED = new Characteristic("2ac1", "Object First-Created", DefaultAttribute.class); - public static final Characteristic OBJECT_ID = new Characteristic("2ac3", "Object ID", DefaultAttribute.class); - public static final Characteristic OBJECT_LAST_MODIFIED = new Characteristic("2ac2", "Object Last-Modified", DefaultAttribute.class); - public static final Characteristic OBJECT_LIST_CONTROL_POINT = new Characteristic("2ac6", "Object List Control Point", DefaultAttribute.class); - public static final Characteristic OBJECT_LIST_FILTER = new Characteristic("2ac7", "Object List Filter", DefaultAttribute.class); - public static final Characteristic OBJECT_NAME = new Characteristic("2abe", "Object Name", DefaultAttribute.class); - public static final Characteristic OBJECT_PROPERTIES = new Characteristic("2ac4", "Object Properties", DefaultAttribute.class); - public static final Characteristic OBJECT_SIZE = new Characteristic("2ac0", "Object Size", DefaultAttribute.class); - public static final Characteristic OBJECT_TYPE = new Characteristic("2abf", "Object Type", DefaultAttribute.class); - public static final Characteristic OTS_FEATURE = new Characteristic("2abd", "OTS Feature", DefaultAttribute.class); - public static final Characteristic PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS = new Characteristic("2a04", "Peripheral Preferred Connection Parameters", DefaultAttribute.class); - public static final Characteristic PERIPHERAL_PRIVACY_FLAG = new Characteristic("2a02", "Peripheral Privacy Flag", DefaultAttribute.class); - public static final Characteristic PLX_CONTINUOUS_MEASUREMENT = new Characteristic("2a5f", "PLX Continuous Measurement Characteristic", DefaultAttribute.class); - public static final Characteristic PLX_FEATURES = new Characteristic("2a60", "PLX Features", DefaultAttribute.class); - public static final Characteristic PLX_SPOT_CHECK_MEASUREMENT = new Characteristic("2a5e", "PLX Spot-Check Measurement", DefaultAttribute.class); - public static final Characteristic PNP_ID = new Characteristic("2a50", "PnP ID", DefaultAttribute.class); - public static final Characteristic POLLEN_CONCENTRATION = new Characteristic("2a75", "Pollen Concentration", DefaultAttribute.class); - public static final Characteristic POSITION_2D = new Characteristic("2a2f", "Position 2D", DefaultAttribute.class); - public static final Characteristic POSITION_3D = new Characteristic("2a30", "Position 3D", DefaultAttribute.class); - public static final Characteristic POSITION_QUALITY = new Characteristic("2a69", "Position Quality", DefaultAttribute.class); - public static final Characteristic PRESSURE = new Characteristic("2a6d", "Pressure", DefaultAttribute.class); - public static final Characteristic PROTOCOL_MODE = new Characteristic("2a4e", "Protocol Mode", DefaultAttribute.class); - public static final Characteristic PULSE_OXIMETRY_CONTROL_POINT = new Characteristic("2a62", "Pulse Oximetry Control Point", DefaultAttribute.class); - public static final Characteristic RAINFALL = new Characteristic("2a78", "Rainfall", DefaultAttribute.class); - public static final Characteristic RECONNECTION_ADDRESS = new Characteristic("2a03", "Reconnection Address", DefaultAttribute.class); - public static final Characteristic RECORD_ACCESS_CONTROL_POINT = new Characteristic("2a52", "Record Access Control Point", DefaultAttribute.class); - public static final Characteristic REFERENCE_TIME_INFORMATION = new Characteristic("2a14", "Reference Time Information", DefaultAttribute.class); - public static final Characteristic REMOVABLE = new Characteristic("2a3a", "Removable", DefaultAttribute.class); - public static final Characteristic REPORT = new Characteristic("2a4d", "Report", DefaultAttribute.class); - public static final Characteristic REPORT_MAP = new Characteristic("2a4b", "Report Map", DefaultAttribute.class); - public static final Characteristic RESOLVABLE_PRIVATE_ADDRESS_ONLY = new Characteristic("2ac9", "Resolvable Private Address Only", DefaultAttribute.class); - public static final Characteristic RESTING_HEART_RATE = new Characteristic("2a92", "Resting Heart Rate", DefaultAttribute.class); - public static final Characteristic RINGER_CONTROL_POINT = new Characteristic("2a40", "Ringer Control point", DefaultAttribute.class); - public static final Characteristic RINGER_SETTING = new Characteristic("2a41", "Ringer Setting", DefaultAttribute.class); - public static final Characteristic ROWER_DATA = new Characteristic("2ad1", "Rower Data", DefaultAttribute.class); - public static final Characteristic RSC_FEATURE = new Characteristic("2a54", "RSC Feature", DefaultAttribute.class); - public static final Characteristic RSC_MEASUREMENT = new Characteristic("2a53", "RSC Measurement", DefaultAttribute.class); - public static final Characteristic SC_CONTROL_POINT = new Characteristic("2a55", "SC Control Point", DefaultAttribute.class); - public static final Characteristic SCAN_INTERVAL_WINDOW = new Characteristic("2a4f", "Scan Interval Window", DefaultAttribute.class); - public static final Characteristic SCAN_REFRESH = new Characteristic("2a31", "Scan Refresh", DefaultAttribute.class); - public static final Characteristic SCIENTIFIC_TEMPERATURE_CELSIUS = new Characteristic("2a3c", "Scientific Temperature Celsius", DefaultAttribute.class); - public static final Characteristic SECONDARY_TIME_ZONE = new Characteristic("2a10", "Secondary Time Zone", DefaultAttribute.class); - public static final Characteristic SENSOR_LOCATION = new Characteristic("2a5d", "Sensor Location", DefaultAttribute.class); - public static final Characteristic SERIAL_NUMBER_STRING = new Characteristic("2a25", "Serial Number String", SerialNumberString.class); - public static final Characteristic SERVICE_CHANGED = new Characteristic("2a05", "Service Changed", DefaultAttribute.class); - public static final Characteristic SERVICE_REQUIRED = new Characteristic("2a3b", "Service Required", DefaultAttribute.class); - public static final Characteristic SOFTWARE_REVISION_STRING = new Characteristic("2a28", "Software Revision String", DefaultAttribute.class); - public static final Characteristic SPORT_TYPE_FOR_AEROBIC_AND_ANAEROBIC_THRESHOLDS = new Characteristic("2a93", "Sport Type for Aerobic and Anaerobic Thresholds", DefaultAttribute.class); - public static final Characteristic STAIR_CLIMBER_DATA = new Characteristic("2ad0", "Stair Climber Data", DefaultAttribute.class); - public static final Characteristic STEP_CLIMBER_DATA = new Characteristic("2acf", "Step Climber Data", DefaultAttribute.class); - public static final Characteristic STRING_ = new Characteristic("2a3d", "String", DefaultAttribute.class); - public static final Characteristic SUPPORTED_HEART_RATE_RANGE = new Characteristic("2ad7", "Supported Heart Rate Range", DefaultAttribute.class); - public static final Characteristic SUPPORTED_INCLINATION_RANGE = new Characteristic("2ad5", "Supported Inclination Range", DefaultAttribute.class); - public static final Characteristic SUPPORTED_NEW_ALERT_CATEGORY = new Characteristic("2a47", "Supported New Alert Category", DefaultAttribute.class); - public static final Characteristic SUPPORTED_POWER_RANGE = new Characteristic("2ad8", "Supported Power Range", DefaultAttribute.class); - public static final Characteristic SUPPORTED_RESISTANCE_LEVEL_RANGE = new Characteristic("2ad6", "Supported Resistance Level Range", DefaultAttribute.class); - public static final Characteristic SUPPORTED_SPEED_RANGE = new Characteristic("2ad4", "Supported Speed Range", DefaultAttribute.class); - public static final Characteristic SUPPORTED_UNREAD_ALERT_CATEGORY = new Characteristic("2a48", "Supported Unread Alert Category", DefaultAttribute.class); - public static final Characteristic SYSTEM_ID = new Characteristic("2a23", "System ID", DefaultAttribute.class); - public static final Characteristic TDS_CONTROL_POINT = new Characteristic("2abc", "TDS Control Point", DefaultAttribute.class); - public static final Characteristic TEMPERATURE = new Characteristic("2a6e", "Temperature", DefaultAttribute.class); - public static final Characteristic TEMPERATURE_CELSIUS = new Characteristic("2a1f", "Temperature Celsius", DefaultAttribute.class); - public static final Characteristic TEMPERATURE_FAHRENHEIT = new Characteristic("2a20", "Temperature Fahrenheit", DefaultAttribute.class); - public static final Characteristic TEMPERATURE_MEASUREMENT = new Characteristic("2a1c", "Temperature Measurement", DefaultAttribute.class); - public static final Characteristic TEMPERATURE_TYPE = new Characteristic("2a1d", "Temperature Type", DefaultAttribute.class); - public static final Characteristic THREE_ZONE_HEART_RATE_LIMITS = new Characteristic("2a94", "Three Zone Heart Rate Limits", DefaultAttribute.class); - public static final Characteristic TIME_ACCURACY = new Characteristic("2a12", "Time Accuracy", DefaultAttribute.class); - public static final Characteristic TIME_BROADCAST = new Characteristic("2a15", "Time Broadcast", DefaultAttribute.class); - public static final Characteristic TIME_SOURCE = new Characteristic("2a13", "Time Source", DefaultAttribute.class); - public static final Characteristic TIME_UPDATE_CONTROL_POINT = new Characteristic("2a16", "Time Update Control Point", DefaultAttribute.class); - public static final Characteristic TIME_UPDATE_STATE = new Characteristic("2a17", "Time Update State", DefaultAttribute.class); - public static final Characteristic TIME_WITH_DST = new Characteristic("2a11", "Time with DST", DefaultAttribute.class); - public static final Characteristic TIME_ZONE = new Characteristic("2a0e", "Time Zone", DefaultAttribute.class); - public static final Characteristic TRAINING_STATUS = new Characteristic("2ad3", "Training Status", DefaultAttribute.class); - public static final Characteristic TREADMILL_DATA = new Characteristic("2acd", "Treadmill Data", DefaultAttribute.class); - public static final Characteristic TRUE_WIND_DIRECTION = new Characteristic("2a71", "True Wind Direction", DefaultAttribute.class); - public static final Characteristic TRUE_WIND_SPEED = new Characteristic("2a70", "True Wind Speed", DefaultAttribute.class); - public static final Characteristic TWO_ZONE_HEART_RATE_LIMIT = new Characteristic("2a95", "Two Zone Heart Rate Limit", DefaultAttribute.class); - public static final Characteristic TX_POWER_LEVEL = new Characteristic("2a07", "Tx Power Level", DefaultAttribute.class); - public static final Characteristic UNCERTAINTY = new Characteristic("2ab4", "Uncertainty", DefaultAttribute.class); - public static final Characteristic UNREAD_ALERT_STATUS = new Characteristic("2a45", "Unread Alert Status", DefaultAttribute.class); - public static final Characteristic URI = new Characteristic("2ab6", "URI", DefaultAttribute.class); - public static final Characteristic USER_CONTROL_POINT = new Characteristic("2a9f", "User Control Point", DefaultAttribute.class); - public static final Characteristic USER_INDEX = new Characteristic("2a9a", "User Index", DefaultAttribute.class); - public static final Characteristic UV_INDEX = new Characteristic("2a76", "UV Index", DefaultAttribute.class); - public static final Characteristic VO2_MAX = new Characteristic("2a96", "VO2 Max", DefaultAttribute.class); - public static final Characteristic WAIST_CIRCUMFERENCE = new Characteristic("2a97", "Waist Circumference", DefaultAttribute.class); - public static final Characteristic WEIGHT = new Characteristic("2a98", "Weight", Weight.class); - public static final Characteristic WEIGHT_MEASUREMENT = new Characteristic("2a9d", "Weight Measurement", DefaultAttribute.class); - public static final Characteristic WEIGHT_SCALE_FEATURE = new Characteristic("2a9e", "Weight Scale Feature", DefaultAttribute.class); - public static final Characteristic WIND_CHILL = new Characteristic("2a79", "Wind Chill", DefaultAttribute.class); + public static final Characteristic DEFAULT = new Characteristic("0000", "Default Characteristic", DefaultAttribute.class); + public static final Characteristic LONGITUDE = new Characteristic("2aaf", "Longitude", DefaultAttribute.class); + public static final Characteristic MAGNETIC_FLUX_DENSITY_2D = new Characteristic("2aa0", "Magnetic Flux Density - 2D", DefaultAttribute.class); + public static final Characteristic MAGNETIC_FLUX_DENSITY_3D = new Characteristic("2aa1", "Magnetic Flux Density - 3D", DefaultAttribute.class); + public static final Characteristic AEROBIC_HEART_RATE_LOWER_LIMIT = new Characteristic("2a7e", "Aerobic Heart Rate Lower Limit", DefaultAttribute.class); + public static final Characteristic AEROBIC_HEART_RATE_UPPER_LIMIT = new Characteristic("2a84", "Aerobic Heart Rate Upper Limit", DefaultAttribute.class); + public static final Characteristic AEROBIC_THRESHOLD = new Characteristic("2a7f", "Aerobic Threshold", DefaultAttribute.class); + public static final Characteristic AGE = new Characteristic("2a80", "Age", Age.class); + public static final Characteristic AGGREGATE = new Characteristic("2a5a", "Aggregate", DefaultAttribute.class); + public static final Characteristic ALERT_CATEGORY_ID = new Characteristic("2a43", "Alert Category ID", DefaultAttribute.class); + public static final Characteristic ALERT_CATEGORY_ID_BIT_MASK = new Characteristic("2a42", "Alert Category ID Bit Mask", DefaultAttribute.class); + public static final Characteristic ALERT_LEVEL = new Characteristic("2a06", "Alert Level", DefaultAttribute.class); + public static final Characteristic ALERT_NOTIFICATION_CONTROL_POINT = new Characteristic("2a44", "Alert Notification Control Point", DefaultAttribute.class); + public static final Characteristic ALERT_STATUS = new Characteristic("2a3f", "Alert Status", DefaultAttribute.class); + public static final Characteristic ALTITUDE = new Characteristic("2ab3", "Altitude", DefaultAttribute.class); + public static final Characteristic ANAEROBIC_HEART_RATE_LOWER_LIMIT = new Characteristic("2a81", "Anaerobic Heart Rate Lower Limit", DefaultAttribute.class); + public static final Characteristic ANAEROBIC_HEART_RATE_UPPER_LIMIT = new Characteristic("2a82", "Anaerobic Heart Rate Upper Limit", DefaultAttribute.class); + public static final Characteristic ANAEROBIC_THRESHOLD = new Characteristic("2a83", "Anaerobic Threshold", DefaultAttribute.class); + public static final Characteristic ANALOG = new Characteristic("2a58", "Analog", DefaultAttribute.class); + public static final Characteristic ANALOG_OUTPUT = new Characteristic("2a59", "Analog Output", DefaultAttribute.class); + public static final Characteristic APPARENT_WIND_DIRECTION = new Characteristic("2a73", "Apparent Wind Direction", DefaultAttribute.class); + public static final Characteristic APPARENT_WIND_SPEED = new Characteristic("2a72", "Apparent Wind Speed", DefaultAttribute.class); + public static final Characteristic APPEARANCE = new Characteristic("2a01", "Appearance", Appearance.class); + public static final Characteristic BAROMETRIC_PRESSURE_TREND = new Characteristic("2aa3", "Barometric Pressure Trend", DefaultAttribute.class); + public static final Characteristic BATTERY_LEVEL = new Characteristic("2a19", "Battery Level", BatteryLevel.class); + public static final Characteristic BATTERY_LEVEL_STATE = new Characteristic("2a1b", "Battery Level State", DefaultAttribute.class); + public static final Characteristic BATTERY_POWER_STATE = new Characteristic("2a1a", "Battery Power State", DefaultAttribute.class); + public static final Characteristic BLOOD_PRESSURE_FEATURE = new Characteristic("2a49", "Blood Pressure Feature", DefaultAttribute.class); + public static final Characteristic BLOOD_PRESSURE_MEASUREMENT = new Characteristic("2a35", "Blood Pressure Measurement", DefaultAttribute.class); + public static final Characteristic BODY_COMPOSITION_FEATURE = new Characteristic("2a9b", "Body Composition Feature", DefaultAttribute.class); + public static final Characteristic BODY_COMPOSITION_MEASUREMENT = new Characteristic("2a9c", "Body Composition Measurement", DefaultAttribute.class); + public static final Characteristic BODY_SENSOR_LOCATION = new Characteristic("2a38", "Body Sensor Location", BodySensorLocation.class); + public static final Characteristic BOND_MANAGEMENT_CONTROL_POINT = new Characteristic("2aa4", "Bond Management Control Point", DefaultAttribute.class); + public static final Characteristic BOND_MANAGEMENT_FEATURE = new Characteristic("2aa5", "Bond Management Features", DefaultAttribute.class); + public static final Characteristic BOOT_KEYBOARD_INPUT_REPORT = new Characteristic("2a22", "Boot Keyboard Input Report", DefaultAttribute.class); + public static final Characteristic BOOT_KEYBOARD_OUTPUT_REPORT = new Characteristic("2a32", "Boot Keyboard Output Report", DefaultAttribute.class); + public static final Characteristic BOOT_MOUSE_INPUT_REPORT = new Characteristic("2a33", "Boot Mouse Input Report", DefaultAttribute.class); + public static final Characteristic CGM_FEATURE = new Characteristic("2aa8", "CGM Feature", DefaultAttribute.class); + public static final Characteristic CGM_MEASUREMENT = new Characteristic("2aa7", "CGM Measurement", DefaultAttribute.class); + public static final Characteristic CGM_SESSION_RUN_TIME = new Characteristic("2aab", "CGM Session Run Time", DefaultAttribute.class); + public static final Characteristic CGM_SESSION_START_TIME = new Characteristic("2aaa", "CGM Session Start Time", DefaultAttribute.class); + public static final Characteristic CGM_SPECIFIC_OPS_CONTROL_POINT = new Characteristic("2aac", "CGM Specific Ops Control Point", DefaultAttribute.class); + public static final Characteristic CGM_STATUS = new Characteristic("2aa9", "CGM Status", DefaultAttribute.class); + public static final Characteristic CROSS_TRAINER_DATA = new Characteristic("2ace", "Cross Trainer Data", DefaultAttribute.class); + public static final Characteristic CSC_FEATURE = new Characteristic("2a5c", "CSC Feature", DefaultAttribute.class); + public static final Characteristic CSC_MEASUREMENT = new Characteristic("2a5b", "CSC Measurement", CyclingSpeedCadenceMeasurement.class); + public static final Characteristic CURRENT_TIME = new Characteristic("2a2b", "Current Time", DefaultAttribute.class); + public static final Characteristic CYCLING_POWER_CONTROL_POINT = new Characteristic("2a66", "Cycling Power Control Point", DefaultAttribute.class); + public static final Characteristic CYCLING_POWER_FEATURE = new Characteristic("2a65", "Cycling Power Feature", DefaultAttribute.class); + public static final Characteristic CYCLING_POWER_MEASUREMENT = new Characteristic("2a63", "Cycling Power Measurement", DefaultAttribute.class); + public static final Characteristic CYCLING_POWER_VECTOR = new Characteristic("2a64", "Cycling Power Vector", DefaultAttribute.class); + public static final Characteristic DATABASE_CHANGE_INCREMENT = new Characteristic("2a99", "Database Change Increment", DefaultAttribute.class); + public static final Characteristic DATE_OF_BIRTH = new Characteristic("2a85", "Date of Birth", DateOfBirth.class); + public static final Characteristic DATE_OF_THRESHOLD_ASSESSMENT = new Characteristic("2a86", "Date of Threshold Assessment", DefaultAttribute.class); + public static final Characteristic DATE_TIME = new Characteristic("2a08", "Date Time", DefaultAttribute.class); + public static final Characteristic DAY_DATE_TIME = new Characteristic("2a0a", "Day Date Time", DefaultAttribute.class); + public static final Characteristic DAY_OF_WEEK = new Characteristic("2a09", "Day of Week", DefaultAttribute.class); + public static final Characteristic DESCRIPTOR_VALUE_CHANGED = new Characteristic("2a7d", "Descriptor Value Changed", DefaultAttribute.class); + public static final Characteristic DEVICE_NAME = new Characteristic("2a00", "Device Name", DeviceName.class); + public static final Characteristic DEW_POINT = new Characteristic("2a7b", "Dew Point", DefaultAttribute.class); + public static final Characteristic DIGITAL = new Characteristic("2a56", "Digital", DefaultAttribute.class); + public static final Characteristic DIGITAL_OUTPUT = new Characteristic("2a57", "Digital Output", DefaultAttribute.class); + public static final Characteristic DST_OFFSET = new Characteristic("2a0d", "DST Offset", DefaultAttribute.class); + public static final Characteristic ELEVATION = new Characteristic("2a6c", "Elevation", DefaultAttribute.class); + public static final Characteristic EMAIL_ADDRESS = new Characteristic("2a87", "Email Address", DefaultAttribute.class); + public static final Characteristic EXACT_TIME_100 = new Characteristic("2a0b", "Exact Time 100", DefaultAttribute.class); + public static final Characteristic EXACT_TIME_256 = new Characteristic("2a0c", "Exact Time 256", DefaultAttribute.class); + public static final Characteristic FAT_BURN_HEART_RATE_LOWER_LIMIT = new Characteristic("2a88", "Fat Burn Heart Rate Lower Limit", DefaultAttribute.class); + public static final Characteristic FAT_BURN_HEART_RATE_UPPER_LIMIT = new Characteristic("2a89", "Fat Burn Heart Rate Upper Limit", DefaultAttribute.class); + public static final Characteristic FIRMWARE_REVISION_STRING = new Characteristic("2a26", "Firmware Revision String", FirmwareRevisionString.class); + public static final Characteristic FIRST_NAME = new Characteristic("2a8a", "First Name", DefaultAttribute.class); + public static final Characteristic FITNESS_MACHINE_CONTROL_POINT = new Characteristic("2ad9", "Fitness Machine Control Point", DefaultAttribute.class); + public static final Characteristic FITNESS_MACHINE_FEATURE = new Characteristic("2acc", "Fitness Machine Feature", DefaultAttribute.class); + public static final Characteristic FITNESS_MACHINE_STATUS = new Characteristic("2ada", "Fitness Machine Status", DefaultAttribute.class); + public static final Characteristic FIVE_ZONE_HEART_RATE_LIMITS = new Characteristic("2a8b", "Five Zone Heart Rate Limits", DefaultAttribute.class); + public static final Characteristic FLOOR_NUMBER = new Characteristic("2ab2", "Floor Number", DefaultAttribute.class); + public static final Characteristic GENDER = new Characteristic("2a8c", "Gender", Gender.class); + public static final Characteristic GLUCOSE_FEATURE = new Characteristic("2a51", "Glucose Feature", DefaultAttribute.class); + public static final Characteristic GLUCOSE_MEASUREMENT = new Characteristic("2a18", "Glucose Measurement", DefaultAttribute.class); + public static final Characteristic GLUCOSE_MEASUREMENT_CONTEXT = new Characteristic("2a34", "Glucose Measurement Context", DefaultAttribute.class); + public static final Characteristic GUST_FACTOR = new Characteristic("2a74", "Gust Factor", DefaultAttribute.class); + public static final Characteristic HARDWARE_REVISION_STRING = new Characteristic("2a27", "Hardware Revision String", DefaultAttribute.class); + public static final Characteristic HEART_RATE_CONTROL_POINT = new Characteristic("2a39", "Heart Rate Control Point", DefaultAttribute.class); + public static final Characteristic HEART_RATE_MAX = new Characteristic("2a8d", "Heart Rate Max", DefaultAttribute.class); + public static final Characteristic HEART_RATE_MEASUREMENT = new Characteristic("2a37", "Heart Rate Measurement", HeartRateMeasurement.class); + public static final Characteristic HEAT_INDEX = new Characteristic("2a7a", "Heat Index", DefaultAttribute.class); + public static final Characteristic HEIGHT = new Characteristic("2a8e", "Height", Height.class); + public static final Characteristic HID_CONTROL_POINT = new Characteristic("2a4c", "HID Control Point", DefaultAttribute.class); + public static final Characteristic HID_INFORMATION = new Characteristic("2a4a", "HID Information", DefaultAttribute.class); + public static final Characteristic HIP_CIRCUMFERENCE = new Characteristic("2a8f", "Hip Circumference", DefaultAttribute.class); + public static final Characteristic HTTP_CONTROL_POINT = new Characteristic("2aba", "HTTP Control Point", DefaultAttribute.class); + public static final Characteristic HTTP_ENTITY_BODY = new Characteristic("2ab9", "HTTP Entity Body", DefaultAttribute.class); + public static final Characteristic HTTP_HEADERS = new Characteristic("2ab7", "HTTP Headers", DefaultAttribute.class); + public static final Characteristic HTTP_STATUS_CODE = new Characteristic("2ab8", "HTTP Status Code", DefaultAttribute.class); + public static final Characteristic HTTPS_SECURITY = new Characteristic("2abb", "HTTPS Security", DefaultAttribute.class); + public static final Characteristic HUMIDITY = new Characteristic("2a6f", "Humidity", DefaultAttribute.class); + public static final Characteristic IEEE_11073_20601_REGULATORY_CERTIFICATION_DATA_LIST = new Characteristic("2a2a", "IEEE 11073-20601 Regulatory Certification Data List", DefaultAttribute.class); + public static final Characteristic INDOOR_BIKE_DATA = new Characteristic("2ad2", "Indoor Bike Data", DefaultAttribute.class); + public static final Characteristic INDOOR_POSITIONING_CONFIGURATION = new Characteristic("2aad", "Indoor Positioning Configuration", DefaultAttribute.class); + public static final Characteristic INTERMEDIATE_CUFF_PRESSURE = new Characteristic("2a36", "Intermediate Cuff Pressure", DefaultAttribute.class); + public static final Characteristic INTERMEDIATE_TEMPERATURE = new Characteristic("2a1e", "Intermediate Temperature", DefaultAttribute.class); + public static final Characteristic IRRADIANCE = new Characteristic("2a77", "Irradiance", DefaultAttribute.class); + public static final Characteristic LANGUAGE = new Characteristic("2aa2", "Language", DefaultAttribute.class); + public static final Characteristic LAST_NAME = new Characteristic("2a90", "Last Name", DefaultAttribute.class); + public static final Characteristic LATITUDE = new Characteristic("2aae", "Latitude", DefaultAttribute.class); + public static final Characteristic LN_CONTROL_POINT = new Characteristic("2a6b", "LN Control Point", DefaultAttribute.class); + public static final Characteristic LN_FEATURE = new Characteristic("2a6a", "LN Feature", DefaultAttribute.class); + public static final Characteristic LOCAL_EAST_COORDINATE = new Characteristic("2ab1", "Local East Coordinate", DefaultAttribute.class); + public static final Characteristic LOCAL_NORTH_COORDINATE = new Characteristic("2ab0", "Local North Coordinate", DefaultAttribute.class); + public static final Characteristic LOCAL_TIME_INFORMATION = new Characteristic("2a0f", "Local Time Information", DefaultAttribute.class); + public static final Characteristic LOCATION_AND_SPEED = new Characteristic("2a67", "Location and Speed Characteristic", DefaultAttribute.class); + public static final Characteristic LOCATION_NAME = new Characteristic("2ab5", "Location Name", DefaultAttribute.class); + public static final Characteristic MAGNETIC_DECLINATION = new Characteristic("2a2c", "Magnetic Declination", DefaultAttribute.class); + public static final Characteristic MANUFACTURER_NAME_STRING = new Characteristic("2a29", "Manufacturer Name String", ManufacturerNameString.class); + public static final Characteristic MAXIMUM_RECOMMENDED_HEART_RATE = new Characteristic("2a91", "Maximum Recommended Heart Rate", DefaultAttribute.class); + public static final Characteristic MEASUREMENT_INTERVAL = new Characteristic("2a21", "Measurement Interval", DefaultAttribute.class); + public static final Characteristic MODEL_NUMBER_STRING = new Characteristic("2a24", "Model Number String", ModelNumberString.class); + public static final Characteristic NAVIGATION = new Characteristic("2a68", "Navigation", DefaultAttribute.class); + public static final Characteristic NETWORK_AVAILABILITY = new Characteristic("2a3e", "Network Availability", DefaultAttribute.class); + public static final Characteristic NEW_ALERT = new Characteristic("2a46", "New Alert", DefaultAttribute.class); + public static final Characteristic OBJECT_ACTION_CONTROL_POINT = new Characteristic("2ac5", "Object Action Control Point", DefaultAttribute.class); + public static final Characteristic OBJECT_CHANGED = new Characteristic("2ac8", "Object Changed", DefaultAttribute.class); + public static final Characteristic OBJECT_FIRST_CREATED = new Characteristic("2ac1", "Object First-Created", DefaultAttribute.class); + public static final Characteristic OBJECT_ID = new Characteristic("2ac3", "Object ID", DefaultAttribute.class); + public static final Characteristic OBJECT_LAST_MODIFIED = new Characteristic("2ac2", "Object Last-Modified", DefaultAttribute.class); + public static final Characteristic OBJECT_LIST_CONTROL_POINT = new Characteristic("2ac6", "Object List Control Point", DefaultAttribute.class); + public static final Characteristic OBJECT_LIST_FILTER = new Characteristic("2ac7", "Object List Filter", DefaultAttribute.class); + public static final Characteristic OBJECT_NAME = new Characteristic("2abe", "Object Name", DefaultAttribute.class); + public static final Characteristic OBJECT_PROPERTIES = new Characteristic("2ac4", "Object Properties", DefaultAttribute.class); + public static final Characteristic OBJECT_SIZE = new Characteristic("2ac0", "Object Size", DefaultAttribute.class); + public static final Characteristic OBJECT_TYPE = new Characteristic("2abf", "Object Type", DefaultAttribute.class); + public static final Characteristic OTS_FEATURE = new Characteristic("2abd", "OTS Feature", DefaultAttribute.class); + public static final Characteristic PERIPHERAL_PREFERRED_CONNECTION_PARAMETERS = new Characteristic("2a04", "Peripheral Preferred Connection Parameters", DefaultAttribute.class); + public static final Characteristic PERIPHERAL_PRIVACY_FLAG = new Characteristic("2a02", "Peripheral Privacy Flag", DefaultAttribute.class); + public static final Characteristic PLX_CONTINUOUS_MEASUREMENT = new Characteristic("2a5f", "PLX Continuous Measurement Characteristic", DefaultAttribute.class); + public static final Characteristic PLX_FEATURES = new Characteristic("2a60", "PLX Features", DefaultAttribute.class); + public static final Characteristic PLX_SPOT_CHECK_MEASUREMENT = new Characteristic("2a5e", "PLX Spot-Check Measurement", DefaultAttribute.class); + public static final Characteristic PNP_ID = new Characteristic("2a50", "PnP ID", DefaultAttribute.class); + public static final Characteristic POLLEN_CONCENTRATION = new Characteristic("2a75", "Pollen Concentration", DefaultAttribute.class); + public static final Characteristic POSITION_2D = new Characteristic("2a2f", "Position 2D", DefaultAttribute.class); + public static final Characteristic POSITION_3D = new Characteristic("2a30", "Position 3D", DefaultAttribute.class); + public static final Characteristic POSITION_QUALITY = new Characteristic("2a69", "Position Quality", DefaultAttribute.class); + public static final Characteristic PRESSURE = new Characteristic("2a6d", "Pressure", DefaultAttribute.class); + public static final Characteristic PROTOCOL_MODE = new Characteristic("2a4e", "Protocol Mode", DefaultAttribute.class); + public static final Characteristic PULSE_OXIMETRY_CONTROL_POINT = new Characteristic("2a62", "Pulse Oximetry Control Point", DefaultAttribute.class); + public static final Characteristic RAINFALL = new Characteristic("2a78", "Rainfall", DefaultAttribute.class); + public static final Characteristic RECONNECTION_ADDRESS = new Characteristic("2a03", "Reconnection Address", DefaultAttribute.class); + public static final Characteristic RECORD_ACCESS_CONTROL_POINT = new Characteristic("2a52", "Record Access Control Point", DefaultAttribute.class); + public static final Characteristic REFERENCE_TIME_INFORMATION = new Characteristic("2a14", "Reference Time Information", DefaultAttribute.class); + public static final Characteristic REMOVABLE = new Characteristic("2a3a", "Removable", DefaultAttribute.class); + public static final Characteristic REPORT = new Characteristic("2a4d", "Report", DefaultAttribute.class); + public static final Characteristic REPORT_MAP = new Characteristic("2a4b", "Report Map", DefaultAttribute.class); + public static final Characteristic RESOLVABLE_PRIVATE_ADDRESS_ONLY = new Characteristic("2ac9", "Resolvable Private Address Only", DefaultAttribute.class); + public static final Characteristic RESTING_HEART_RATE = new Characteristic("2a92", "Resting Heart Rate", DefaultAttribute.class); + public static final Characteristic RINGER_CONTROL_POINT = new Characteristic("2a40", "Ringer Control point", DefaultAttribute.class); + public static final Characteristic RINGER_SETTING = new Characteristic("2a41", "Ringer Setting", DefaultAttribute.class); + public static final Characteristic ROWER_DATA = new Characteristic("2ad1", "Rower Data", DefaultAttribute.class); + public static final Characteristic RSC_FEATURE = new Characteristic("2a54", "RSC Feature", DefaultAttribute.class); + public static final Characteristic RSC_MEASUREMENT = new Characteristic("2a53", "RSC Measurement", DefaultAttribute.class); + public static final Characteristic SC_CONTROL_POINT = new Characteristic("2a55", "SC Control Point", DefaultAttribute.class); + public static final Characteristic SCAN_INTERVAL_WINDOW = new Characteristic("2a4f", "Scan Interval Window", DefaultAttribute.class); + public static final Characteristic SCAN_REFRESH = new Characteristic("2a31", "Scan Refresh", DefaultAttribute.class); + public static final Characteristic SCIENTIFIC_TEMPERATURE_CELSIUS = new Characteristic("2a3c", "Scientific Temperature Celsius", DefaultAttribute.class); + public static final Characteristic SECONDARY_TIME_ZONE = new Characteristic("2a10", "Secondary Time Zone", DefaultAttribute.class); + public static final Characteristic SENSOR_LOCATION = new Characteristic("2a5d", "Sensor Location", DefaultAttribute.class); + public static final Characteristic SERIAL_NUMBER_STRING = new Characteristic("2a25", "Serial Number String", SerialNumberString.class); + public static final Characteristic SERVICE_CHANGED = new Characteristic("2a05", "Service Changed", DefaultAttribute.class); + public static final Characteristic SERVICE_REQUIRED = new Characteristic("2a3b", "Service Required", DefaultAttribute.class); + public static final Characteristic SOFTWARE_REVISION_STRING = new Characteristic("2a28", "Software Revision String", DefaultAttribute.class); + public static final Characteristic SPORT_TYPE_FOR_AEROBIC_AND_ANAEROBIC_THRESHOLDS = new Characteristic("2a93", "Sport Type for Aerobic and Anaerobic Thresholds", DefaultAttribute.class); + public static final Characteristic STAIR_CLIMBER_DATA = new Characteristic("2ad0", "Stair Climber Data", DefaultAttribute.class); + public static final Characteristic STEP_CLIMBER_DATA = new Characteristic("2acf", "Step Climber Data", DefaultAttribute.class); + public static final Characteristic STRING_ = new Characteristic("2a3d", "String", DefaultAttribute.class); + public static final Characteristic SUPPORTED_HEART_RATE_RANGE = new Characteristic("2ad7", "Supported Heart Rate Range", DefaultAttribute.class); + public static final Characteristic SUPPORTED_INCLINATION_RANGE = new Characteristic("2ad5", "Supported Inclination Range", DefaultAttribute.class); + public static final Characteristic SUPPORTED_NEW_ALERT_CATEGORY = new Characteristic("2a47", "Supported New Alert Category", DefaultAttribute.class); + public static final Characteristic SUPPORTED_POWER_RANGE = new Characteristic("2ad8", "Supported Power Range", DefaultAttribute.class); + public static final Characteristic SUPPORTED_RESISTANCE_LEVEL_RANGE = new Characteristic("2ad6", "Supported Resistance Level Range", DefaultAttribute.class); + public static final Characteristic SUPPORTED_SPEED_RANGE = new Characteristic("2ad4", "Supported Speed Range", DefaultAttribute.class); + public static final Characteristic SUPPORTED_UNREAD_ALERT_CATEGORY = new Characteristic("2a48", "Supported Unread Alert Category", DefaultAttribute.class); + public static final Characteristic SYSTEM_ID = new Characteristic("2a23", "System ID", DefaultAttribute.class); + public static final Characteristic TDS_CONTROL_POINT = new Characteristic("2abc", "TDS Control Point", DefaultAttribute.class); + public static final Characteristic TEMPERATURE = new Characteristic("2a6e", "Temperature", DefaultAttribute.class); + public static final Characteristic TEMPERATURE_CELSIUS = new Characteristic("2a1f", "Temperature Celsius", DefaultAttribute.class); + public static final Characteristic TEMPERATURE_FAHRENHEIT = new Characteristic("2a20", "Temperature Fahrenheit", DefaultAttribute.class); + public static final Characteristic TEMPERATURE_MEASUREMENT = new Characteristic("2a1c", "Temperature Measurement", DefaultAttribute.class); + public static final Characteristic TEMPERATURE_TYPE = new Characteristic("2a1d", "Temperature Type", DefaultAttribute.class); + public static final Characteristic THREE_ZONE_HEART_RATE_LIMITS = new Characteristic("2a94", "Three Zone Heart Rate Limits", DefaultAttribute.class); + public static final Characteristic TIME_ACCURACY = new Characteristic("2a12", "Time Accuracy", DefaultAttribute.class); + public static final Characteristic TIME_BROADCAST = new Characteristic("2a15", "Time Broadcast", DefaultAttribute.class); + public static final Characteristic TIME_SOURCE = new Characteristic("2a13", "Time Source", DefaultAttribute.class); + public static final Characteristic TIME_UPDATE_CONTROL_POINT = new Characteristic("2a16", "Time Update Control Point", DefaultAttribute.class); + public static final Characteristic TIME_UPDATE_STATE = new Characteristic("2a17", "Time Update State", DefaultAttribute.class); + public static final Characteristic TIME_WITH_DST = new Characteristic("2a11", "Time with DST", DefaultAttribute.class); + public static final Characteristic TIME_ZONE = new Characteristic("2a0e", "Time Zone", DefaultAttribute.class); + public static final Characteristic TRAINING_STATUS = new Characteristic("2ad3", "Training Status", DefaultAttribute.class); + public static final Characteristic TREADMILL_DATA = new Characteristic("2acd", "Treadmill Data", DefaultAttribute.class); + public static final Characteristic TRUE_WIND_DIRECTION = new Characteristic("2a71", "True Wind Direction", DefaultAttribute.class); + public static final Characteristic TRUE_WIND_SPEED = new Characteristic("2a70", "True Wind Speed", DefaultAttribute.class); + public static final Characteristic TWO_ZONE_HEART_RATE_LIMIT = new Characteristic("2a95", "Two Zone Heart Rate Limit", DefaultAttribute.class); + public static final Characteristic TX_POWER_LEVEL = new Characteristic("2a07", "Tx Power Level", DefaultAttribute.class); + public static final Characteristic UNCERTAINTY = new Characteristic("2ab4", "Uncertainty", DefaultAttribute.class); + public static final Characteristic UNREAD_ALERT_STATUS = new Characteristic("2a45", "Unread Alert Status", DefaultAttribute.class); + public static final Characteristic URI = new Characteristic("2ab6", "URI", DefaultAttribute.class); + public static final Characteristic USER_CONTROL_POINT = new Characteristic("2a9f", "User Control Point", DefaultAttribute.class); + public static final Characteristic USER_INDEX = new Characteristic("2a9a", "User Index", DefaultAttribute.class); + public static final Characteristic UV_INDEX = new Characteristic("2a76", "UV Index", DefaultAttribute.class); + public static final Characteristic VO2_MAX = new Characteristic("2a96", "VO2 Max", DefaultAttribute.class); + public static final Characteristic WAIST_CIRCUMFERENCE = new Characteristic("2a97", "Waist Circumference", DefaultAttribute.class); + public static final Characteristic WEIGHT = new Characteristic("2a98", "Weight", Weight.class); + public static final Characteristic WEIGHT_MEASUREMENT = new Characteristic("2a9d", "Weight Measurement", DefaultAttribute.class); + public static final Characteristic WEIGHT_SCALE_FEATURE = new Characteristic("2a9e", "Weight Scale Feature", DefaultAttribute.class); + public static final Characteristic WIND_CHILL = new Characteristic("2a79", "Wind Chill", DefaultAttribute.class); - private static UuidObjectMap characteristics = new UuidObjectMap(); + private static UuidObjectMap> characteristics = new UuidObjectMap>(); static { @@ -462,9 +463,9 @@ public class Characteristics * @param uuid the UUID to look for * @return the corresponding characteristic or the DEFAULT characteristic of smartgattlib if UUID is not found */ - public static Characteristic lookup(UUID uuid) + public static Characteristic lookup(UUID uuid) { - Characteristic result = characteristics.get(uuid); + Characteristic result = characteristics.get(uuid); return result == null ? DEFAULT : result; } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java index 545e2df..2a6e2ef 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Age.java @@ -8,7 +8,7 @@ public class Age extends AbstractReadWriteAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.AGE; + public static final Characteristic CHARACTERISTIC = Characteristics.AGE; private Short age; @@ -38,7 +38,7 @@ public Age(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java index 4cdd06c..08c9935 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java @@ -8,7 +8,7 @@ public class Appearance extends AbstractReadAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.APPEARANCE; + public static final Characteristic CHARACTERISTIC = Characteristics.APPEARANCE; private Short category; @@ -30,7 +30,7 @@ public Appearance(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java index 6b5e3c5..2d74b00 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/BatteryLevel.java @@ -8,7 +8,7 @@ public class BatteryLevel extends AbstractReadAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.BATTERY_LEVEL; + public static final Characteristic CHARACTERISTIC = Characteristics.BATTERY_LEVEL; private Double level; @@ -30,7 +30,7 @@ public BatteryLevel(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java index 7719a60..ba5cd8a 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java @@ -8,7 +8,7 @@ public class DateOfBirth extends AbstractReadWriteAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.DATE_OF_BIRTH; + public static final Characteristic CHARACTERISTIC = Characteristics.DATE_OF_BIRTH; private Integer year; private Short month; @@ -66,7 +66,7 @@ public DateOfBirth(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java index aa42156..db2ccc1 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java @@ -8,7 +8,7 @@ public class DeviceName extends AbstractReadAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.DEVICE_NAME; + public static final Characteristic CHARACTERISTIC = Characteristics.DEVICE_NAME; private String name; @@ -30,7 +30,7 @@ public DeviceName(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java index ae6bcb3..729da76 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java @@ -8,7 +8,7 @@ public class FirmwareRevisionString extends AbstractReadAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.FIRMWARE_REVISION_STRING; + public static final Characteristic CHARACTERISTIC = Characteristics.FIRMWARE_REVISION_STRING; private String firmware_Revision; @@ -30,7 +30,7 @@ public FirmwareRevisionString(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java index 3cb185e..f495c85 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Gender.java @@ -8,7 +8,7 @@ public class Gender extends AbstractReadWriteAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.GENDER; + public static final Characteristic CHARACTERISTIC = Characteristics.GENDER; private EnumGender gender; @@ -38,7 +38,7 @@ public Gender(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java index b39e176..0b33616 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java @@ -8,7 +8,7 @@ public class Height extends AbstractReadWriteAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.HEIGHT; + public static final Characteristic CHARACTERISTIC = Characteristics.HEIGHT; private Double heigt; @@ -46,7 +46,7 @@ public Height(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java index d074b3b..8fc5b74 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java @@ -8,7 +8,7 @@ public class ManufacturerNameString extends AbstractReadAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.MANUFACTURER_NAME_STRING; + public static final Characteristic CHARACTERISTIC = Characteristics.MANUFACTURER_NAME_STRING; private String manufacturer_Name; @@ -30,7 +30,7 @@ public ManufacturerNameString(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java index 28684ca..4dbb893 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java @@ -8,7 +8,7 @@ public class ModelNumberString extends AbstractReadAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.MODEL_NUMBER_STRING; + public static final Characteristic CHARACTERISTIC = Characteristics.MODEL_NUMBER_STRING; private String model_Number; @@ -30,7 +30,7 @@ public ModelNumberString(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java index ca4e78c..5f29c91 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java @@ -8,7 +8,7 @@ public class SerialNumberString extends AbstractReadAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.SERIAL_NUMBER_STRING; + public static final Characteristic CHARACTERISTIC = Characteristics.SERIAL_NUMBER_STRING; private String serial_Number; @@ -30,7 +30,7 @@ public SerialNumberString(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java index 949b6d7..65afd65 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java @@ -8,7 +8,7 @@ public class Weight extends AbstractReadWriteAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.WEIGHT; + public static final Characteristic CHARACTERISTIC = Characteristics.WEIGHT; private Double weight; @@ -46,7 +46,7 @@ public Weight(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src/main/java/com/movisens/smartgattlib/attributes/BodySensorLocation.java b/src/main/java/com/movisens/smartgattlib/attributes/BodySensorLocation.java index 3b7b70a..a11443c 100644 --- a/src/main/java/com/movisens/smartgattlib/attributes/BodySensorLocation.java +++ b/src/main/java/com/movisens/smartgattlib/attributes/BodySensorLocation.java @@ -11,7 +11,7 @@ public enum Location { Other, Chest, Wrist, Finger, Hand, Ear_Lobe, Foot; } - public static final Characteristic CHARACTERISTIC = Characteristics.BODY_SENSOR_LOCATION; + public static final Characteristic CHARACTERISTIC = Characteristics.BODY_SENSOR_LOCATION; private Location location; @@ -57,7 +57,7 @@ public BodySensorLocation(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java index c5e7e23..5a6260f 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java +++ b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java @@ -10,7 +10,7 @@ public byte[] getBytes() return data; } - public abstract Characteristic getCharacteristic(); + public abstract Characteristic getCharacteristic(); public boolean isReadable() { diff --git a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java index bcfa7f1..606553b 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java +++ b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java @@ -2,12 +2,12 @@ import com.movisens.smartgattlib.attributes.DefaultAttribute; -public class Characteristic extends UuidObject +public class Characteristic extends UuidObject { - private Class attributeClass; + private Class attributeClass; - public Characteristic(String uuid, String name, Class attributeClass) + public Characteristic(String uuid, String name, Class attributeClass) { super(uuid, name); this.attributeClass = attributeClass; @@ -26,7 +26,7 @@ public AbstractAttribute createAttribute(byte[] data) } } - public Class getAttributeClass() + public Class getAttributeClass() { return attributeClass; } From 5cf91a8a9afed2c392bd21e66fce9ebf6fe75d64 Mon Sep 17 00:00:00 2001 From: grossmann Date: Wed, 7 Mar 2018 17:18:58 +0100 Subject: [PATCH 28/60] fixed imports --- .../movisens/smartgattlib/Characteristics.java | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java index b35217f..4c74cf5 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java +++ b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java @@ -2,25 +2,11 @@ import java.util.UUID; -import com.movisens.smartgattlib.attributes.Age; -import com.movisens.smartgattlib.attributes.Appearance; -import com.movisens.smartgattlib.attributes.BatteryLevel; -import com.movisens.smartgattlib.attributes.BodySensorLocation; -import com.movisens.smartgattlib.attributes.CyclingSpeedCadenceMeasurement; -import com.movisens.smartgattlib.attributes.DateOfBirth; import com.movisens.smartgattlib.attributes.DefaultAttribute; -import com.movisens.smartgattlib.attributes.DeviceName; -import com.movisens.smartgattlib.attributes.FirmwareRevisionString; -import com.movisens.smartgattlib.attributes.Gender; -import com.movisens.smartgattlib.attributes.HeartRateMeasurement; -import com.movisens.smartgattlib.attributes.Height; -import com.movisens.smartgattlib.attributes.ManufacturerNameString; -import com.movisens.smartgattlib.attributes.ModelNumberString; -import com.movisens.smartgattlib.attributes.SerialNumberString; -import com.movisens.smartgattlib.attributes.Weight; -import com.movisens.smartgattlib.helper.AbstractAttribute; import com.movisens.smartgattlib.helper.Characteristic; import com.movisens.smartgattlib.helper.UuidObjectMap; +import com.movisens.smartgattlib.helper.AbstractAttribute; +import com.movisens.smartgattlib.attributes.*; public class Characteristics { From 99a62b11997dd47c9d0d45805ce58ada68d9d204 Mon Sep 17 00:00:00 2001 From: grossmann Date: Wed, 7 Mar 2018 17:22:45 +0100 Subject: [PATCH 29/60] parameterized references to generic type Characteristic --- .../attributes/CyclingSpeedCadenceMeasurement.java | 4 ++-- .../smartgattlib/attributes/DefaultAttribute.java | 2 +- .../attributes/HeartRateMeasurement.java | 4 ++-- src/test/java/WeightTest.java | 12 +++++------- 4 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/movisens/smartgattlib/attributes/CyclingSpeedCadenceMeasurement.java b/src/main/java/com/movisens/smartgattlib/attributes/CyclingSpeedCadenceMeasurement.java index 614e8db..773a87c 100644 --- a/src/main/java/com/movisens/smartgattlib/attributes/CyclingSpeedCadenceMeasurement.java +++ b/src/main/java/com/movisens/smartgattlib/attributes/CyclingSpeedCadenceMeasurement.java @@ -8,7 +8,7 @@ public class CyclingSpeedCadenceMeasurement extends AbstractReadAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.CSC_MEASUREMENT; + public static final Characteristic CHARACTERISTIC = Characteristics.CSC_MEASUREMENT; public static final int MAX_CUMULATIVE_CRANK_REVS = 65535; public static final long MAX_CUMULATIVE_WHEEL_REVS = 4294967295L; @@ -75,7 +75,7 @@ private boolean crankRevPresent(byte flags) { } @Override - public Characteristic getCharacteristic() { + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src/main/java/com/movisens/smartgattlib/attributes/DefaultAttribute.java b/src/main/java/com/movisens/smartgattlib/attributes/DefaultAttribute.java index c13afee..0853c26 100644 --- a/src/main/java/com/movisens/smartgattlib/attributes/DefaultAttribute.java +++ b/src/main/java/com/movisens/smartgattlib/attributes/DefaultAttribute.java @@ -13,7 +13,7 @@ public DefaultAttribute(byte[] data) } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return Characteristics.DEFAULT; } diff --git a/src/main/java/com/movisens/smartgattlib/attributes/HeartRateMeasurement.java b/src/main/java/com/movisens/smartgattlib/attributes/HeartRateMeasurement.java index 9615329..cb72a7e 100644 --- a/src/main/java/com/movisens/smartgattlib/attributes/HeartRateMeasurement.java +++ b/src/main/java/com/movisens/smartgattlib/attributes/HeartRateMeasurement.java @@ -11,7 +11,7 @@ public class HeartRateMeasurement extends AbstractReadAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.HEART_RATE_MEASUREMENT; + public static final Characteristic CHARACTERISTIC = Characteristics.HEART_RATE_MEASUREMENT; ArrayList rrIntervals = new ArrayList(); int hrmval = 0; @@ -97,7 +97,7 @@ public SensorWorn getSensorWorn() { } @Override - public Characteristic getCharacteristic() + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src/test/java/WeightTest.java b/src/test/java/WeightTest.java index a43b0a3..1ad9644 100644 --- a/src/test/java/WeightTest.java +++ b/src/test/java/WeightTest.java @@ -1,12 +1,10 @@ -import com.movisens.smartgattlib.attributes.Weight; -import com.movisens.smartgattlib.helper.GattByteBuffer; -import org.junit.Test; - -import java.util.Arrays; - import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.movisens.smartgattlib.attributes.Weight; +import com.movisens.smartgattlib.helper.GattByteBuffer; /** * Created by robert.zetzsche on 17.05.2017. From c1818ff3abe3ea135c080e38c35dbac696560b4f Mon Sep 17 00:00:00 2001 From: grossmann Date: Thu, 8 Mar 2018 08:51:34 +0100 Subject: [PATCH 30/60] suppressed warning in example --- src/test/java/Example.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/Example.java b/src/test/java/Example.java index 571705f..fb82d93 100644 --- a/src/test/java/Example.java +++ b/src/test/java/Example.java @@ -40,6 +40,7 @@ public static void main(String[] args) { } // write Attribute + @SuppressWarnings("unused") AbstractAttribute aa = new Weight(12.3); // TODO: Write aa.getBytes() to aa.getCharacteristic().getUuid(); } From ecd04e9c1fbdacfb0c6b7c17bc2e9df8cbf0e2ac Mon Sep 17 00:00:00 2001 From: grossmann Date: Thu, 8 Mar 2018 09:27:05 +0100 Subject: [PATCH 31/60] renamed CyclingSpeedCadenceMeasurement to CscMeasurement --- .gitignore | 1 + .../java/com/movisens/smartgattlib/Characteristics.java | 2 +- ...ngSpeedCadenceMeasurement.java => CscMeasurement.java} | 8 ++++---- src/test/java/CyclingSpeedCadenceMeasurementTest.java | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) rename src/main/java/com/movisens/smartgattlib/attributes/{CyclingSpeedCadenceMeasurement.java => CscMeasurement.java} (88%) diff --git a/.gitignore b/.gitignore index 66e779a..e632471 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ bin/ gen/ MANIFEST.MF build.num +*.java_ # Local configuration file (sdk path, etc) local.properties diff --git a/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java index 4c74cf5..5a27b0f 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java +++ b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java @@ -56,7 +56,7 @@ public class Characteristics public static final Characteristic CGM_STATUS = new Characteristic("2aa9", "CGM Status", DefaultAttribute.class); public static final Characteristic CROSS_TRAINER_DATA = new Characteristic("2ace", "Cross Trainer Data", DefaultAttribute.class); public static final Characteristic CSC_FEATURE = new Characteristic("2a5c", "CSC Feature", DefaultAttribute.class); - public static final Characteristic CSC_MEASUREMENT = new Characteristic("2a5b", "CSC Measurement", CyclingSpeedCadenceMeasurement.class); + public static final Characteristic CSC_MEASUREMENT = new Characteristic("2a5b", "CSC Measurement", CscMeasurement.class); public static final Characteristic CURRENT_TIME = new Characteristic("2a2b", "Current Time", DefaultAttribute.class); public static final Characteristic CYCLING_POWER_CONTROL_POINT = new Characteristic("2a66", "Cycling Power Control Point", DefaultAttribute.class); public static final Characteristic CYCLING_POWER_FEATURE = new Characteristic("2a65", "Cycling Power Feature", DefaultAttribute.class); diff --git a/src/main/java/com/movisens/smartgattlib/attributes/CyclingSpeedCadenceMeasurement.java b/src/main/java/com/movisens/smartgattlib/attributes/CscMeasurement.java similarity index 88% rename from src/main/java/com/movisens/smartgattlib/attributes/CyclingSpeedCadenceMeasurement.java rename to src/main/java/com/movisens/smartgattlib/attributes/CscMeasurement.java index 773a87c..b532fcc 100644 --- a/src/main/java/com/movisens/smartgattlib/attributes/CyclingSpeedCadenceMeasurement.java +++ b/src/main/java/com/movisens/smartgattlib/attributes/CscMeasurement.java @@ -6,9 +6,9 @@ import com.movisens.smartgattlib.helper.Characteristic; import com.movisens.smartgattlib.helper.GattByteBuffer; -public class CyclingSpeedCadenceMeasurement extends AbstractReadAttribute { +public class CscMeasurement extends AbstractReadAttribute { - public static final Characteristic CHARACTERISTIC = Characteristics.CSC_MEASUREMENT; + public static final Characteristic CHARACTERISTIC = Characteristics.CSC_MEASUREMENT; public static final int MAX_CUMULATIVE_CRANK_REVS = 65535; public static final long MAX_CUMULATIVE_WHEEL_REVS = 4294967295L; @@ -21,7 +21,7 @@ public class CyclingSpeedCadenceMeasurement extends AbstractReadAttribute { private int cumulativeCrankRevolutions; private int lastCrankEventTime; - public CyclingSpeedCadenceMeasurement(byte[] data) { + public CscMeasurement(byte[] data) { this.data = data; GattByteBuffer bb = GattByteBuffer.wrap(data); @@ -75,7 +75,7 @@ private boolean crankRevPresent(byte flags) { } @Override - public Characteristic getCharacteristic() { + public Characteristic getCharacteristic() { return CHARACTERISTIC; } diff --git a/src/test/java/CyclingSpeedCadenceMeasurementTest.java b/src/test/java/CyclingSpeedCadenceMeasurementTest.java index 221c461..0b6cd9a 100644 --- a/src/test/java/CyclingSpeedCadenceMeasurementTest.java +++ b/src/test/java/CyclingSpeedCadenceMeasurementTest.java @@ -1,5 +1,5 @@ import com.movisens.smartgattlib.GattUtils; -import com.movisens.smartgattlib.attributes.CyclingSpeedCadenceMeasurement; +import com.movisens.smartgattlib.attributes.CscMeasurement; import org.junit.Test; @@ -10,7 +10,7 @@ public class CyclingSpeedCadenceMeasurementTest { @Test public void testCCM() { byte[] testValue = GattUtils.hexStringToByteArray("032D060000A3A9900A289F"); - CyclingSpeedCadenceMeasurement ccm = new CyclingSpeedCadenceMeasurement(testValue); + CscMeasurement ccm = new CscMeasurement(testValue); assertTrue("Wheel rev should be present", ccm.isWheelRevPresent()); assertTrue("Crank rev should be present", ccm.isCrankRevPresent()); assertTrue("Cumulative crank revolutions should be 2704", ccm.getCumulativeCrankRevolutions() == 2704); From 1eed2c7b5b1297df4fcf229b1746ef36ccea3f56 Mon Sep 17 00:00:00 2001 From: grossmann Date: Sun, 15 Jul 2018 14:49:42 +0200 Subject: [PATCH 32/60] added toString to AbstractAttribute --- .../smartgattlib/helper/AbstractAttribute.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java index 5a6260f..f02db8b 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java +++ b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java @@ -21,5 +21,17 @@ public boolean isWritable() { return false; } + + @Override + public String toString() + { + String result = ""; + for(byte b : data) + { + result += String.format("0x%02x ", b); + } + + return result; + } } From 1b5b181beea92b3a4090e1aff55b665a1e197534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Fri, 7 Dec 2018 15:14:42 +0100 Subject: [PATCH 33/60] added getValues to UuidObjectMap --- .../com/movisens/smartgattlib/helper/UuidObjectMap.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/movisens/smartgattlib/helper/UuidObjectMap.java b/src/main/java/com/movisens/smartgattlib/helper/UuidObjectMap.java index c0d81c2..bf2f1a1 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/UuidObjectMap.java +++ b/src/main/java/com/movisens/smartgattlib/helper/UuidObjectMap.java @@ -1,5 +1,6 @@ package com.movisens.smartgattlib.helper; +import java.util.Collection; import java.util.HashMap; import java.util.UUID; @@ -17,4 +18,9 @@ public T get(UUID uuid) { return map.get(uuid); } + + public Collection getValues() + { + return map.values(); + } } From 180bddbd2c204f3b7d12e6aeb893cb37272f74f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Fri, 7 Dec 2018 15:20:31 +0100 Subject: [PATCH 34/60] updated version to 3.0.8 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 120bbec..a091621 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.0.6' +version = '3.0.8' repositories { mavenCentral() From 4f53b3277295ca77673f1a0ec3acd8e491352450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Thu, 21 Feb 2019 13:56:12 +0100 Subject: [PATCH 35/60] added getCharacteristics to Service --- .../com/movisens/smartgattlib/Characteristics.java | 13 ++++++++++++- .../smartgattlib/attributes/DateOfBirth.java | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java index 5a27b0f..910649b 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java +++ b/src-gen/main/java/com/movisens/smartgattlib/Characteristics.java @@ -1,5 +1,6 @@ package com.movisens.smartgattlib; +import java.util.Collection; import java.util.UUID; import com.movisens.smartgattlib.attributes.DefaultAttribute; @@ -442,7 +443,17 @@ public class Characteristics characteristics.put(WEIGHT_SCALE_FEATURE); characteristics.put(WIND_CHILL); } - + + /** + * Get all Characteristics. + * + * @return all available Characteristics + */ + public static Collection> getCharacteristics() + { + return characteristics.getValues(); + } + /** * Looks up the characteristic for the given UUID. If the UUID is not found, a lookup in the smartgattlib is performed. * diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java index ba5cd8a..d67b979 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/DateOfBirth.java @@ -74,6 +74,6 @@ public Characteristic getCharacteristic() @Override public String toString() { - return "year = " + getYear() + ", " + "month = " + getMonth() + ", " + "day = " + getDay(); + return "year = " + getYear().toString() + ", " + "month = " + getMonth().toString() + ", " + "day = " + getDay().toString(); } } From 4a1833c2bac3191e1f7e7470c0899dce9d7be64a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Thu, 21 Feb 2019 14:13:48 +0100 Subject: [PATCH 36/60] updated version and changelog --- CHANGELOG.md | 5 +++++ build.gradle | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e832c2..fb890b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ + +# [3.1.0] (2019-02-21) + + - Added getCharacteristics to obtain all Characteristics as a Collection + # [3.0.0](https://github.com/movisens/SmartGattLib/compare/v2.1...v3.0) (2017-11-07) diff --git a/build.gradle b/build.gradle index a091621..ae04034 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.0.8' +version = '3.1.0' repositories { mavenCentral() From 3a2135b6bdc16c5d29f30e576c51bf2585ca3806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrgen=20Stumpp?= Date: Fri, 15 Mar 2019 15:38:49 +0100 Subject: [PATCH 37/60] Export all packages for OSGi closes https://github.com/movisens/SmartGattLib/issues/10 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ae04034..d520944 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ jar { attributes ( 'Bundle-SymbolicName': project.group + project.name, 'Bundle-Version': project.version, - 'Export-Package': 'com.movisens.smartgattlib' + 'Export-Package': 'com.movisens.smartgattlib, com.movisens.smartgattlib.*' ) } } From 2b8112f78075a5e887f77afbf25069faf5c41418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Tue, 3 Sep 2019 22:07:18 +0200 Subject: [PATCH 38/60] added putMsTime to GattByteBuffer --- CHANGELOG.md | 5 +++++ build.gradle | 2 +- .../com/movisens/smartgattlib/helper/GattByteBuffer.java | 5 +++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fb890b6..403ac34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ + +# [3.2.0] (2019-09-03) + + - Added putMstime to GattByteBuffer + # [3.1.0] (2019-02-21) diff --git a/build.gradle b/build.gradle index ae04034..ad22446 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.1.0' +version = '3.2.0' repositories { mavenCentral() diff --git a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java index a96472d..16c22ce 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java +++ b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java @@ -239,6 +239,11 @@ public Date getStime() return new Date(getUint32()*1000); } + public void putMstime(Date date) + { + putInt64(date.getTime()); + } + public Date getMstime() { return new Date(getInt64()); From 2dd4a9de7858f7e66e394137fee6569fe1cdaaff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Mon, 16 Sep 2019 16:46:12 +0200 Subject: [PATCH 39/60] added TimeZone to GattByteBuffer --- .../smartgattlib/helper/GattByteBuffer.java | 15 ++++++ .../smartgattlib/helper/TimeZoneUtil.java | 51 +++++++++++++++++++ .../helper/GattByteBufferTest.java | 29 +++++++++++ 3 files changed, 95 insertions(+) create mode 100644 src/main/java/com/movisens/smartgattlib/helper/TimeZoneUtil.java create mode 100644 src/test/java/com/movisens/smartgattlib/helper/GattByteBufferTest.java diff --git a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java index 16c22ce..260607b 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java +++ b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java @@ -2,6 +2,7 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.time.ZoneId; import java.util.Date; import java.util.UUID; @@ -249,6 +250,20 @@ public Date getMstime() return new Date(getInt64()); } + public void putTimeZone(java.time.ZoneId zoneId) + { + String ltzs = zoneId.getId(); + String stzs = TimeZoneUtil.toShortTimeZone(ltzs); + putString(stzs); + } + + public java.time.ZoneId getTimeZone() + { + String stzs = getString(); + String ltzs = TimeZoneUtil.toLongTimeZone(stzs); + return ZoneId.of(ltzs); + } + public UUID getUuid() { int length = buffer.remaining(); diff --git a/src/main/java/com/movisens/smartgattlib/helper/TimeZoneUtil.java b/src/main/java/com/movisens/smartgattlib/helper/TimeZoneUtil.java new file mode 100644 index 0000000..382ccda --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/helper/TimeZoneUtil.java @@ -0,0 +1,51 @@ +package com.movisens.smartgattlib.helper; + +import java.util.HashMap; +import java.util.Map.Entry; + +public class TimeZoneUtil +{ + + private static HashMap getTransformMap() + { + HashMap map = new HashMap(); + map.put("ARG", "America/Argentina"); + map.put("AM", "America"); + map.put("EU", "Europe"); + map.put("AU", "Australia"); + map.put("ANT", "Antarctica"); + map.put("ND", "North_Dakota"); + map.put("KY", "Kentucky"); + map.put("/IN", "/Indiana"); + map.put("ATC", "Atlantic"); + map.put("AFR", "Africa"); + map.put("PFC", "Pacific"); + return map; + } + + public static String toLongTimeZone(String timeZoneString) + { + String id = timeZoneString; + HashMap map = getTransformMap(); + + for (Entry e : map.entrySet()) + { + id = id.replaceAll(e.getKey(), e.getValue()); + } + + return id; + } + + public static String toShortTimeZone(String timeZoneString) + { + String id = timeZoneString; + HashMap map = getTransformMap(); + + for (Entry e : map.entrySet()) + { + id = id.replaceAll(e.getValue(), e.getKey()); + } + + return id; + } +} diff --git a/src/test/java/com/movisens/smartgattlib/helper/GattByteBufferTest.java b/src/test/java/com/movisens/smartgattlib/helper/GattByteBufferTest.java new file mode 100644 index 0000000..82cbe58 --- /dev/null +++ b/src/test/java/com/movisens/smartgattlib/helper/GattByteBufferTest.java @@ -0,0 +1,29 @@ +package com.movisens.smartgattlib.helper; + +import static org.junit.Assert.assertTrue; + +import java.time.ZoneId; +import java.util.Set; + +import org.junit.Test; + + +public class GattByteBufferTest +{ + + @Test + public void testPutTimeZone() + { + Set zIds = ZoneId.getAvailableZoneIds(); + for (String zId : zIds) + { + GattByteBuffer bb = GattByteBuffer.allocate(20); + ZoneId ptz = ZoneId.of(zId); + bb.putTimeZone(ptz); + bb.rewind(); + ZoneId gtz = bb.getTimeZone(); + assertTrue(gtz.equals(ptz)); + } + } + +} From fae1c8a5460f3b171bbf06ed11ff6edb2d12b118 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Mon, 16 Sep 2019 16:51:23 +0200 Subject: [PATCH 40/60] fixed typo --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index bb83c0d..a58074a 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.2.0' +version = '3.3.0' repositories { mavenCentral() From a275886e32aa1ff7ad9fe15dc731e783988768e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Mon, 16 Sep 2019 16:51:32 +0200 Subject: [PATCH 41/60] fixed typo --- .../java/com/movisens/smartgattlib/helper/GattByteBuffer.java | 4 ++-- .../com/movisens/smartgattlib/helper/GattByteBufferTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java index 260607b..5fe4a39 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java +++ b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java @@ -250,14 +250,14 @@ public Date getMstime() return new Date(getInt64()); } - public void putTimeZone(java.time.ZoneId zoneId) + public void putTimezone(java.time.ZoneId zoneId) { String ltzs = zoneId.getId(); String stzs = TimeZoneUtil.toShortTimeZone(ltzs); putString(stzs); } - public java.time.ZoneId getTimeZone() + public java.time.ZoneId getTimezone() { String stzs = getString(); String ltzs = TimeZoneUtil.toLongTimeZone(stzs); diff --git a/src/test/java/com/movisens/smartgattlib/helper/GattByteBufferTest.java b/src/test/java/com/movisens/smartgattlib/helper/GattByteBufferTest.java index 82cbe58..542f66a 100644 --- a/src/test/java/com/movisens/smartgattlib/helper/GattByteBufferTest.java +++ b/src/test/java/com/movisens/smartgattlib/helper/GattByteBufferTest.java @@ -19,9 +19,9 @@ public void testPutTimeZone() { GattByteBuffer bb = GattByteBuffer.allocate(20); ZoneId ptz = ZoneId.of(zId); - bb.putTimeZone(ptz); + bb.putTimezone(ptz); bb.rewind(); - ZoneId gtz = bb.getTimeZone(); + ZoneId gtz = bb.getTimezone(); assertTrue(gtz.equals(ptz)); } } From 61ab40a48fe537fcd8d459aa6ef3a9f9e5b595fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Tue, 17 Sep 2019 16:00:06 +0200 Subject: [PATCH 42/60] setTimezone and getTimezone use String --- build.gradle | 2 +- .../com/movisens/smartgattlib/helper/GattByteBuffer.java | 9 ++++----- .../movisens/smartgattlib/helper/GattByteBufferTest.java | 8 ++++---- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index a58074a..d605f77 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.3.0' +version = '3.4.0' repositories { mavenCentral() diff --git a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java index 5fe4a39..ce9c76e 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java +++ b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java @@ -250,18 +250,17 @@ public Date getMstime() return new Date(getInt64()); } - public void putTimezone(java.time.ZoneId zoneId) + public void putTimezone(String zoneId) { - String ltzs = zoneId.getId(); - String stzs = TimeZoneUtil.toShortTimeZone(ltzs); + String stzs = TimeZoneUtil.toShortTimeZone(zoneId); putString(stzs); } - public java.time.ZoneId getTimezone() + public String getTimezone() { String stzs = getString(); String ltzs = TimeZoneUtil.toLongTimeZone(stzs); - return ZoneId.of(ltzs); + return ltzs; } public UUID getUuid() diff --git a/src/test/java/com/movisens/smartgattlib/helper/GattByteBufferTest.java b/src/test/java/com/movisens/smartgattlib/helper/GattByteBufferTest.java index 542f66a..bc20065 100644 --- a/src/test/java/com/movisens/smartgattlib/helper/GattByteBufferTest.java +++ b/src/test/java/com/movisens/smartgattlib/helper/GattByteBufferTest.java @@ -18,11 +18,11 @@ public void testPutTimeZone() for (String zId : zIds) { GattByteBuffer bb = GattByteBuffer.allocate(20); - ZoneId ptz = ZoneId.of(zId); - bb.putTimezone(ptz); + + bb.putTimezone(zId); bb.rewind(); - ZoneId gtz = bb.getTimezone(); - assertTrue(gtz.equals(ptz)); + String gtz = bb.getTimezone(); + assertTrue(gtz.equals(zId)); } } From 155ae660746187d2ad27af7a302414d9dde4aaf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Fri, 13 Dec 2019 00:00:57 +0100 Subject: [PATCH 43/60] fixed double to int conversion when lsbValue is used --- build.gradle | 2 +- .../main/java/com/movisens/smartgattlib/attributes/Height.java | 2 +- .../main/java/com/movisens/smartgattlib/attributes/Weight.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index d605f77..b8dc89f 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.4.0' +version = '3.4.1' repositories { mavenCentral() diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java index 0b33616..96ea9bc 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Height.java @@ -34,7 +34,7 @@ public Height(Double heigt) } this.heigt = heigt; GattByteBuffer bb = GattByteBuffer.allocate(2); - bb.putUint16(new Double(heigt / 0.01).intValue()); + bb.putUint16(new Long(Math.round(heigt / 0.01)).intValue()); this.data = bb.array(); } diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java index 65afd65..0c8e4cf 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Weight.java @@ -34,7 +34,7 @@ public Weight(Double weight) } this.weight = weight; GattByteBuffer bb = GattByteBuffer.allocate(2); - bb.putUint16(new Double(weight / 0.005).intValue()); + bb.putUint16(new Long(Math.round(weight / 0.005)).intValue()); this.data = bb.array(); } From 1a61cb8e707f56991636f612fd636322545a4bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Mon, 25 May 2020 21:27:01 +0200 Subject: [PATCH 44/60] added required characteristics to Characteristic --- build.gradle | 2 +- .../smartgattlib/helper/Characteristic.java | 13 ++++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index b8dc89f..3ac72c4 100644 --- a/build.gradle +++ b/build.gradle @@ -21,7 +21,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.4.1' +version = '3.5.0' repositories { mavenCentral() diff --git a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java index 606553b..49cf9a7 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java +++ b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java @@ -4,13 +4,20 @@ public class Characteristic extends UuidObject { - private Class attributeClass; + + Characteristic requiredCharacteristics[]; + + public Characteristic[] getRequiredCharacteristics() + { + return requiredCharacteristics; + } - public Characteristic(String uuid, String name, Class attributeClass) + public Characteristic(String uuid, String name, Class attributeClass, Characteristic... requiredCharacteristics) { super(uuid, name); - this.attributeClass = attributeClass; + this.attributeClass = attributeClass; + this.requiredCharacteristics = requiredCharacteristics; } public AbstractAttribute createAttribute(byte[] data) From f0fce32e0707255e6c4d87f9709f6c6d60d7d637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Thu, 16 Dec 2021 18:11:10 +0100 Subject: [PATCH 45/60] added security features --- CHANGELOG.md | 7 + build.gradle | 21 +-- gradle/wrapper/gradle-wrapper.properties | 2 +- .../smartgattlib/attributes/Appearance.java | 3 +- .../smartgattlib/attributes/DeviceName.java | 3 +- .../attributes/FirmwareRevisionString.java | 3 +- .../attributes/ManufacturerNameString.java | 3 +- .../attributes/SerialNumberString.java | 3 +- .../helper/AbstractAttribute.java | 31 +++- .../smartgattlib/helper/Characteristic.java | 41 +++++- .../helper/PlainTextAttribute.java | 11 ++ .../smartgattlib/security/AesUtil.java | 139 ++++++++++++++++++ .../smartgattlib/security/CryptoManager.java | 42 ++++++ .../security/CryptoManagerProvider.java | 18 +++ .../smartgattlib/security/KeyGenerator.java | 21 +++ src/test/java/WeightTest.java | 6 +- .../smartgattlib/security/AesUtilTest.java | 81 ++++++++++ 17 files changed, 397 insertions(+), 38 deletions(-) create mode 100644 src/main/java/com/movisens/smartgattlib/helper/PlainTextAttribute.java create mode 100644 src/main/java/com/movisens/smartgattlib/security/AesUtil.java create mode 100644 src/main/java/com/movisens/smartgattlib/security/CryptoManager.java create mode 100644 src/main/java/com/movisens/smartgattlib/security/CryptoManagerProvider.java create mode 100644 src/main/java/com/movisens/smartgattlib/security/KeyGenerator.java create mode 100644 src/test/java/com/movisens/smartgattlib/security/AesUtilTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 403ac34..3bd42b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ + +# [4.0.0] + +- allowed encryption of over the air data +- the method getBytes of AbstractAttribute was renamed to getOutgoingData +- to get the raw data representation of an attribute the method getRawData was added to AbstractAttribute + # [3.2.0] (2019-09-03) diff --git a/build.gradle b/build.gradle index 3ac72c4..bca4ae0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,17 +1,4 @@ -/* - * This build file was auto generated by running the Gradle 'init' task - * by 'Juergen' at '15.03.15 19:57' with Gradle 1.11 - * - * This generated file contains a sample Java project to get you started. - * For more details take a look at the Java Quickstart chapter in the Gradle - * user guide available at http://gradle.org/docs/1.11/userguide/tutorial_java_projects.html - */ - -plugins { - id "org.jruyi.osgibnd" version "0.5.0" -} - -// java plugin implied by the osgibnd plugin +apply plugin: 'java' apply plugin: 'eclipse' apply plugin: 'idea' apply plugin: 'maven' @@ -21,7 +8,7 @@ group = 'com.github.movisens' targetCompatibility = 1.6 sourceCompatibility = 1.6 -version = '3.5.0' +version = '4.0.0-SNAPSHOT' repositories { mavenCentral() @@ -49,7 +36,3 @@ jar { ) } } - -task wrapper(type: Wrapper) { - gradleVersion = '4.3' -} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 39a584a..6c0fd3c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java index 08c9935..d3280d2 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/Appearance.java @@ -4,8 +4,9 @@ import com.movisens.smartgattlib.helper.AbstractReadAttribute; import com.movisens.smartgattlib.helper.Characteristic; import com.movisens.smartgattlib.helper.GattByteBuffer; +import com.movisens.smartgattlib.helper.PlainTextAttribute; -public class Appearance extends AbstractReadAttribute +public class Appearance extends AbstractReadAttribute implements PlainTextAttribute { public static final Characteristic CHARACTERISTIC = Characteristics.APPEARANCE; diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java index db2ccc1..c7438c2 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/DeviceName.java @@ -4,8 +4,9 @@ import com.movisens.smartgattlib.helper.AbstractReadAttribute; import com.movisens.smartgattlib.helper.Characteristic; import com.movisens.smartgattlib.helper.GattByteBuffer; +import com.movisens.smartgattlib.helper.PlainTextAttribute; -public class DeviceName extends AbstractReadAttribute +public class DeviceName extends AbstractReadAttribute implements PlainTextAttribute { public static final Characteristic CHARACTERISTIC = Characteristics.DEVICE_NAME; diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java index 729da76..c063df0 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/FirmwareRevisionString.java @@ -4,8 +4,9 @@ import com.movisens.smartgattlib.helper.AbstractReadAttribute; import com.movisens.smartgattlib.helper.Characteristic; import com.movisens.smartgattlib.helper.GattByteBuffer; +import com.movisens.smartgattlib.helper.PlainTextAttribute; -public class FirmwareRevisionString extends AbstractReadAttribute +public class FirmwareRevisionString extends AbstractReadAttribute implements PlainTextAttribute { public static final Characteristic CHARACTERISTIC = Characteristics.FIRMWARE_REVISION_STRING; diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java index 8fc5b74..de92313 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/ManufacturerNameString.java @@ -4,8 +4,9 @@ import com.movisens.smartgattlib.helper.AbstractReadAttribute; import com.movisens.smartgattlib.helper.Characteristic; import com.movisens.smartgattlib.helper.GattByteBuffer; +import com.movisens.smartgattlib.helper.PlainTextAttribute; -public class ManufacturerNameString extends AbstractReadAttribute +public class ManufacturerNameString extends AbstractReadAttribute implements PlainTextAttribute { public static final Characteristic CHARACTERISTIC = Characteristics.MANUFACTURER_NAME_STRING; diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java index 5f29c91..725563c 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/SerialNumberString.java @@ -4,8 +4,9 @@ import com.movisens.smartgattlib.helper.AbstractReadAttribute; import com.movisens.smartgattlib.helper.Characteristic; import com.movisens.smartgattlib.helper.GattByteBuffer; +import com.movisens.smartgattlib.helper.PlainTextAttribute; -public class SerialNumberString extends AbstractReadAttribute +public class SerialNumberString extends AbstractReadAttribute implements PlainTextAttribute { public static final Characteristic CHARACTERISTIC = Characteristics.SERIAL_NUMBER_STRING; diff --git a/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java index f02db8b..91c0f16 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java +++ b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java @@ -1,11 +1,35 @@ package com.movisens.smartgattlib.helper; +import com.movisens.smartgattlib.security.CryptoManagerProvider; + public abstract class AbstractAttribute { protected byte[] data; - public byte[] getBytes() + /** + * Get data to be sent via BLE. This data may be encrypted. + * + * @return data to send via BLE + */ + public byte[] getOutgoingData() + { + if (getCharacteristic().isEncryptionAllowed()) + { + return CryptoManagerProvider.get().processBeforeSend(data); + } + else + { + return data; + } + } + + /** + * Gets the raw data representation of this attribute. This data is not encrypted. + * + * @return raw adta representation + */ + public byte[] getRawData() { return data; } @@ -21,17 +45,16 @@ public boolean isWritable() { return false; } - + @Override public String toString() { String result = ""; - for(byte b : data) + for (byte b : data) { result += String.format("0x%02x ", b); } return result; } - } diff --git a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java index 49cf9a7..c27d950 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java +++ b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java @@ -1,11 +1,17 @@ package com.movisens.smartgattlib.helper; +import java.lang.reflect.Constructor; +import java.util.Arrays; +import java.util.function.Predicate; + import com.movisens.smartgattlib.attributes.DefaultAttribute; +import com.movisens.smartgattlib.security.CryptoManagerProvider; public class Characteristic extends UuidObject { + private Class attributeClass; - + Characteristic requiredCharacteristics[]; public Characteristic[] getRequiredCharacteristics() @@ -20,27 +26,50 @@ public Characteristic(String uuid, String name, Class attributeClass, Charact this.requiredCharacteristics = requiredCharacteristics; } - public AbstractAttribute createAttribute(byte[] data) + public AbstractAttribute createAttribute(byte[] incommingData) { try { - return attributeClass.getConstructor(byte[].class).newInstance(data); + Constructor constructor = attributeClass.getConstructor(byte[].class); + + if (isEncryptionAllowed()) + { + return constructor.newInstance(CryptoManagerProvider.get().processAfterReceive(incommingData)); + } + else + { + return constructor.newInstance(incommingData); + } } catch (Throwable e) { e.printStackTrace(); - return new DefaultAttribute(data); + return new DefaultAttribute(incommingData); } } - + public Class getAttributeClass() { return attributeClass; } - + + public boolean isEncryptionAllowed() + { + return Arrays.stream(attributeClass.getInterfaces()).filter(new Predicate>() + { + + @Override + public boolean test(Class i) + { + return i.getName().equals(PlainTextAttribute.class.getName()); + } + }).count() == 0; + } + @Override public String toString() { return getName(); } + } diff --git a/src/main/java/com/movisens/smartgattlib/helper/PlainTextAttribute.java b/src/main/java/com/movisens/smartgattlib/helper/PlainTextAttribute.java new file mode 100644 index 0000000..c2c5cb8 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/helper/PlainTextAttribute.java @@ -0,0 +1,11 @@ +package com.movisens.smartgattlib.helper; + +/** + * Attributes that implements the PlainTextAttribute interface will not be encrypted. + * + * @author ulrich.grossmann + * + */ +public interface PlainTextAttribute +{ +} diff --git a/src/main/java/com/movisens/smartgattlib/security/AesUtil.java b/src/main/java/com/movisens/smartgattlib/security/AesUtil.java new file mode 100644 index 0000000..15fce67 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/security/AesUtil.java @@ -0,0 +1,139 @@ +package com.movisens.smartgattlib.security; + +import java.security.spec.KeySpec; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import javax.crypto.spec.SecretKeySpec; + +public class AesUtil +{ + + public static SecretKey createAesKey(String password) + { + try + { + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256"); + KeySpec spec = new PBEKeySpec(password.toCharArray(), password.getBytes(), 65536, 128); + SecretKey secret = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), "AES"); + return secret; + } + catch (Throwable t) + { + throw new RuntimeException(t.getMessage()); + } + } + + public static byte[] encrypt(byte[] input, SecretKey key) + { + if (input.length % 16 == 0) + { + return encryptBlocks(input, key); + } + else if (input.length < 16) + { + byte[] d = new byte[16]; + Arrays.fill(d, (byte) 0); + System.arraycopy(input, 0, d, 0, input.length); + return encryptBlocks(d, key); + } + else if (input.length > 16 && input.length < 32) + { + return encryptCtsTail(input, key); + } + else + { + throw new RuntimeException("input len not supported"); + } + } + + public static byte[] decrypt(byte[] input, SecretKey key) + { + if (input.length % 16 == 0) + { + return decryptBlocks(input, key); + } + else if (input.length > 16 && input.length < 32) + { + return decryptCtsTail(input, key); + } + else + { + throw new RuntimeException("input len not supported"); + } + } + + public static byte[] encryptCtsTail(byte[] plainText, SecretKey key) + { + // ciphertext stealing (CTS) + + // input.length 17 .. 31 + + byte[] b1 = Arrays.copyOfRange(plainText, 0, 16); + byte[] c1 = encryptBlocks(b1, key); + + byte[] b2 = new byte[16]; + System.arraycopy(plainText, 16, b2, 0, plainText.length - 16); + System.arraycopy(c1, plainText.length - 16, b2, plainText.length - 16, 32 - plainText.length); + byte[] c2 = encryptBlocks(b2, key); + + byte[] cipherText = new byte[plainText.length]; + System.arraycopy(c1, 0, cipherText, 0, plainText.length - 16); + System.arraycopy(c2, 0, cipherText, plainText.length - 16, 16); + + return cipherText; + } + + public static byte[] decryptCtsTail(byte[] cipherText, SecretKey key) + { + // ciphertext stealing (CTS) + + // input.length 17 .. 31 + + byte[] b1 = Arrays.copyOfRange(cipherText, cipherText.length - 16, cipherText.length); + byte[] c1 = decryptBlocks(b1, key); + + byte[] b2 = new byte[16]; + System.arraycopy(cipherText, 0, b2, 0, cipherText.length - 16); + System.arraycopy(c1, cipherText.length - 16, b2, cipherText.length - 16, 32 - cipherText.length); + byte[] c2 = decryptBlocks(b2, key); + + byte[] plainText = new byte[cipherText.length]; + System.arraycopy(c1, 0, plainText, 16, cipherText.length - 16); + System.arraycopy(c2, 0, plainText, 0, 16); + + return plainText; + } + + public static byte[] encryptBlocks(byte[] input, SecretKey key) + { + try + { + Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); + cipher.init(Cipher.ENCRYPT_MODE, key); + return cipher.doFinal(input); + } + catch (Throwable e) + { + throw new RuntimeException(e.getMessage()); + } + } + + public static byte[] decryptBlocks(byte[] cipherText, SecretKey key) + { + try + { + Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding"); + cipher.init(Cipher.DECRYPT_MODE, key); + return cipher.doFinal(cipherText); + } + catch (Throwable e) + { + throw new RuntimeException(e.getMessage()); + } + } + +} diff --git a/src/main/java/com/movisens/smartgattlib/security/CryptoManager.java b/src/main/java/com/movisens/smartgattlib/security/CryptoManager.java new file mode 100644 index 0000000..a019e35 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/security/CryptoManager.java @@ -0,0 +1,42 @@ +package com.movisens.smartgattlib.security; + +import javax.crypto.SecretKey; + +public class CryptoManager +{ + private SecretKey secretKey = null; + + public void setPassword(String password) + { + secretKey = AesUtil.createAesKey(password); + } + + public void removePassword() + { + secretKey = null; + } + + public byte[] processBeforeSend(byte[] data) + { + if (secretKey != null) + { + return AesUtil.encrypt(data, secretKey); + } + else + { + return data; + } + } + + public byte[] processAfterReceive(byte[] data) + { + if (secretKey != null) + { + return AesUtil.decrypt(data, secretKey); + } + else + { + return data; + } + } +} diff --git a/src/main/java/com/movisens/smartgattlib/security/CryptoManagerProvider.java b/src/main/java/com/movisens/smartgattlib/security/CryptoManagerProvider.java new file mode 100644 index 0000000..4be0fa7 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/security/CryptoManagerProvider.java @@ -0,0 +1,18 @@ +package com.movisens.smartgattlib.security; + + +public class CryptoManagerProvider +{ + private static CryptoManager cryptoManager = new CryptoManager(); + + public static CryptoManager get() + { + return CryptoManagerProvider.cryptoManager; + } + + public static void setSecurityManager(CryptoManager cryptoManager) + { + CryptoManagerProvider.cryptoManager = cryptoManager; + } + +} diff --git a/src/main/java/com/movisens/smartgattlib/security/KeyGenerator.java b/src/main/java/com/movisens/smartgattlib/security/KeyGenerator.java new file mode 100644 index 0000000..fc8e9d4 --- /dev/null +++ b/src/main/java/com/movisens/smartgattlib/security/KeyGenerator.java @@ -0,0 +1,21 @@ +package com.movisens.smartgattlib.security; + +import javax.crypto.SecretKey; + +import com.movisens.smartgattlib.helper.GattByteBuffer; + +public class KeyGenerator +{ + public static long[] createKey(String password) { + + SecretKey key = AesUtil.createAesKey(password); + GattByteBuffer bb = GattByteBuffer.wrap(key.getEncoded()); + + long[] result = new long[2]; + result[0] = bb.getInt64(); + result[1] = bb.getInt64(); + + return result; + } + +} diff --git a/src/test/java/WeightTest.java b/src/test/java/WeightTest.java index 1ad9644..604ee91 100644 --- a/src/test/java/WeightTest.java +++ b/src/test/java/WeightTest.java @@ -19,8 +19,8 @@ public void testWeight() { assertEquals(weight.getWeight(), weightFloat, 0); assertEquals(weightBytes.getWeight(), weightFloat.doubleValue(), 0); assertEquals(weightBytes.getWeight(), weight.getWeight()); - assertArrayEquals(weight.getBytes(), bytes); - assertArrayEquals(weightBytes.getBytes(), bytes); - assertArrayEquals(weight.getBytes(), weightBytes.getBytes()); + assertArrayEquals(weight.getRawData(), bytes); + assertArrayEquals(weightBytes.getRawData(), bytes); + assertArrayEquals(weight.getRawData(), weightBytes.getRawData()); } } diff --git a/src/test/java/com/movisens/smartgattlib/security/AesUtilTest.java b/src/test/java/com/movisens/smartgattlib/security/AesUtilTest.java new file mode 100644 index 0000000..6578837 --- /dev/null +++ b/src/test/java/com/movisens/smartgattlib/security/AesUtilTest.java @@ -0,0 +1,81 @@ +package com.movisens.smartgattlib.security; + +import static org.junit.Assert.*; + +import javax.crypto.SecretKey; + +import org.junit.Test; + +import com.movisens.smartgattlib.security.AesUtil; + + +public class AesUtilTest +{ + static SecretKey key = AesUtil.createAesKey("secret"); + + @Test + public void testSmallBlock() + { + String plainText = "abcdefgh"; + + byte[] ciperText = AesUtil.encrypt(plainText.getBytes(), key); + assertEquals(16, ciperText.length); + + byte[] resultText = AesUtil.decrypt(ciperText, key); + String resultString = new String(resultText, 0, plainText.length()); + assertEquals(plainText, resultString); + } + + @Test + public void testMultibleOf16k() + { + String plainText = "123456781234567812345678123456781234567812345678"; + + byte[] ciperText = AesUtil.encrypt(plainText.getBytes(), key); + assertEquals(plainText.length(), ciperText.length); + + byte[] resultText = AesUtil.decrypt(ciperText, key); + String resultString = new String(resultText, 0, plainText.length()); + assertEquals(plainText, resultString); + } + + @Test + public void testLen17() + { + String plainText = "12345678123456781"; + + byte[] ciperText = AesUtil.encrypt(plainText.getBytes(), key); + assertEquals(plainText.length(), ciperText.length); + + byte[] resultText = AesUtil.decrypt(ciperText, key); + String resultString = new String(resultText, 0, plainText.length()); + assertEquals(plainText, resultString); + } + + @Test + public void testLen20() + { + String plainText = "123456781234567812"; + + byte[] ciperText = AesUtil.encrypt(plainText.getBytes(), key); + assertEquals(plainText.length(), ciperText.length); + + byte[] resultText = AesUtil.decrypt(ciperText, key); + String resultString = new String(resultText, 0, plainText.length()); + assertEquals(plainText, resultString); + } + + @Test + public void testLen31() + { + String plainText = "1234567812345678123456781234567"; + + byte[] ciperText = AesUtil.encrypt(plainText.getBytes(), key); + assertEquals(plainText.length(), ciperText.length); + + byte[] resultText = AesUtil.decrypt(ciperText, key); + String resultString = new String(resultText, 0, plainText.length()); + assertEquals(plainText, resultString); + } + +} From 1c3cfcc7277f8338f64c7b1989c01d3ed25be251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Fri, 17 Dec 2021 12:31:16 +0100 Subject: [PATCH 46/60] added initialize to cryptoManager --- .../com/movisens/smartgattlib/security/CryptoManager.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/movisens/smartgattlib/security/CryptoManager.java b/src/main/java/com/movisens/smartgattlib/security/CryptoManager.java index a019e35..4cfc7b4 100644 --- a/src/main/java/com/movisens/smartgattlib/security/CryptoManager.java +++ b/src/main/java/com/movisens/smartgattlib/security/CryptoManager.java @@ -11,7 +11,12 @@ public void setPassword(String password) secretKey = AesUtil.createAesKey(password); } - public void removePassword() + public void initialize() + { + disableEncryption(); + } + + public void disableEncryption() { secretKey = null; } From 7d72131132f4fb516cab410da87f52fae976aefc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Wed, 29 Dec 2021 17:05:47 +0100 Subject: [PATCH 47/60] removed CryptoManagerProvider, aes key is provided externaly --- CHANGELOG.md | 5 +++-- .../smartgattlib/helper/AbstractAttribute.java | 6 +++--- .../smartgattlib/helper/Characteristic.java | 6 +++--- .../smartgattlib/helper/GattByteBuffer.java | 1 - .../smartgattlib/security/AesUtil.java | 13 +++++++++++++ .../smartgattlib/security/CryptoManager.java | 12 +++++++++--- .../security/CryptoManagerProvider.java | 18 ------------------ src/test/java/Example.java | 14 ++++++++++---- .../smartgattlib/security/AesUtilTest.java | 4 +--- 9 files changed, 42 insertions(+), 37 deletions(-) delete mode 100644 src/main/java/com/movisens/smartgattlib/security/CryptoManagerProvider.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bd42b4..cc60e35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,9 @@ # [4.0.0] -- allowed encryption of over the air data -- the method getBytes of AbstractAttribute was renamed to getOutgoingData +- allowed encryption of over the air data +- the method getBytes of AbstractAttribute was renamed to getOutgoingData and needs CryptoManager as argument +- changed signature Characteristic.createAttribute: CryptoManager was added - to get the raw data representation of an attribute the method getRawData was added to AbstractAttribute diff --git a/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java index 91c0f16..1ddfbe4 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java +++ b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java @@ -1,6 +1,6 @@ package com.movisens.smartgattlib.helper; -import com.movisens.smartgattlib.security.CryptoManagerProvider; +import com.movisens.smartgattlib.security.CryptoManager; public abstract class AbstractAttribute { @@ -12,11 +12,11 @@ public abstract class AbstractAttribute * * @return data to send via BLE */ - public byte[] getOutgoingData() + public byte[] getOutgoingData(CryptoManager cryptoManager) { if (getCharacteristic().isEncryptionAllowed()) { - return CryptoManagerProvider.get().processBeforeSend(data); + return cryptoManager.processBeforeSend(data); } else { diff --git a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java index c27d950..4e77d01 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java +++ b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java @@ -5,7 +5,7 @@ import java.util.function.Predicate; import com.movisens.smartgattlib.attributes.DefaultAttribute; -import com.movisens.smartgattlib.security.CryptoManagerProvider; +import com.movisens.smartgattlib.security.CryptoManager; public class Characteristic extends UuidObject { @@ -26,7 +26,7 @@ public Characteristic(String uuid, String name, Class attributeClass, Charact this.requiredCharacteristics = requiredCharacteristics; } - public AbstractAttribute createAttribute(byte[] incommingData) + public AbstractAttribute createAttribute(CryptoManager cryptoManager, byte[] incommingData) { try { @@ -34,7 +34,7 @@ public AbstractAttribute createAttribute(byte[] incommingData) if (isEncryptionAllowed()) { - return constructor.newInstance(CryptoManagerProvider.get().processAfterReceive(incommingData)); + return constructor.newInstance(cryptoManager.processAfterReceive(incommingData)); } else { diff --git a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java index ce9c76e..cde22b7 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java +++ b/src/main/java/com/movisens/smartgattlib/helper/GattByteBuffer.java @@ -2,7 +2,6 @@ import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.time.ZoneId; import java.util.Date; import java.util.UUID; diff --git a/src/main/java/com/movisens/smartgattlib/security/AesUtil.java b/src/main/java/com/movisens/smartgattlib/security/AesUtil.java index 15fce67..b485881 100644 --- a/src/main/java/com/movisens/smartgattlib/security/AesUtil.java +++ b/src/main/java/com/movisens/smartgattlib/security/AesUtil.java @@ -27,6 +27,19 @@ public static SecretKey createAesKey(String password) } } + public static SecretKey createAesKey(byte[] aesKey) + { + try + { + SecretKey secret = new SecretKeySpec(aesKey, "AES"); + return secret; + } + catch (Throwable t) + { + throw new RuntimeException(t.getMessage()); + } + } + public static byte[] encrypt(byte[] input, SecretKey key) { if (input.length % 16 == 0) diff --git a/src/main/java/com/movisens/smartgattlib/security/CryptoManager.java b/src/main/java/com/movisens/smartgattlib/security/CryptoManager.java index 4cfc7b4..2c9fb3f 100644 --- a/src/main/java/com/movisens/smartgattlib/security/CryptoManager.java +++ b/src/main/java/com/movisens/smartgattlib/security/CryptoManager.java @@ -4,23 +4,29 @@ public class CryptoManager { + private SecretKey secretKey = null; - public void setPassword(String password) + public void setKey(byte[] secretKey) { - secretKey = AesUtil.createAesKey(password); + this.secretKey = AesUtil.createAesKey(secretKey); } public void initialize() { disableEncryption(); } - + public void disableEncryption() { secretKey = null; } + public boolean encryptionEnabled() + { + return secretKey != null; + } + public byte[] processBeforeSend(byte[] data) { if (secretKey != null) diff --git a/src/main/java/com/movisens/smartgattlib/security/CryptoManagerProvider.java b/src/main/java/com/movisens/smartgattlib/security/CryptoManagerProvider.java deleted file mode 100644 index 4be0fa7..0000000 --- a/src/main/java/com/movisens/smartgattlib/security/CryptoManagerProvider.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.movisens.smartgattlib.security; - - -public class CryptoManagerProvider -{ - private static CryptoManager cryptoManager = new CryptoManager(); - - public static CryptoManager get() - { - return CryptoManagerProvider.cryptoManager; - } - - public static void setSecurityManager(CryptoManager cryptoManager) - { - CryptoManagerProvider.cryptoManager = cryptoManager; - } - -} diff --git a/src/test/java/Example.java b/src/test/java/Example.java index fb82d93..42f29f1 100644 --- a/src/test/java/Example.java +++ b/src/test/java/Example.java @@ -1,8 +1,12 @@ import java.util.UUID; -import com.movisens.smartgattlib.*; -import com.movisens.smartgattlib.attributes.*; -import com.movisens.smartgattlib.helper.*; +import com.movisens.smartgattlib.Characteristics; +import com.movisens.smartgattlib.Services; +import com.movisens.smartgattlib.attributes.DefaultAttribute; +import com.movisens.smartgattlib.attributes.HeartRateMeasurement; +import com.movisens.smartgattlib.attributes.Weight; +import com.movisens.smartgattlib.helper.AbstractAttribute; +import com.movisens.smartgattlib.security.CryptoManager; public class Example { @@ -28,7 +32,9 @@ public static void main(String[] args) { UUID uuid = null;// characteristic.getUuid(); byte[] data = null;// characteristic.getValue(); - AbstractAttribute a = Characteristics.lookup(uuid).createAttribute(data); + CryptoManager cryptoManager = new CryptoManager(); + + AbstractAttribute a = Characteristics.lookup(uuid).createAttribute(cryptoManager, data); if (a instanceof HeartRateMeasurement) { HeartRateMeasurement heartRateMeasurement = ((HeartRateMeasurement) a); heartRateMeasurement.getHr(); diff --git a/src/test/java/com/movisens/smartgattlib/security/AesUtilTest.java b/src/test/java/com/movisens/smartgattlib/security/AesUtilTest.java index 6578837..7e550ac 100644 --- a/src/test/java/com/movisens/smartgattlib/security/AesUtilTest.java +++ b/src/test/java/com/movisens/smartgattlib/security/AesUtilTest.java @@ -1,13 +1,11 @@ package com.movisens.smartgattlib.security; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; import javax.crypto.SecretKey; import org.junit.Test; -import com.movisens.smartgattlib.security.AesUtil; - public class AesUtilTest { From d500fbadffb768455a125a563df509fa160cace2 Mon Sep 17 00:00:00 2001 From: "ulrich.grossmann" Date: Tue, 12 Jul 2022 15:09:10 +0200 Subject: [PATCH 48/60] moved out of pc_suite --- build.gradle | 7 ++----- gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 0 settings.gradle | 18 ------------------ 4 files changed, 3 insertions(+), 24 deletions(-) mode change 100644 => 100755 gradlew diff --git a/build.gradle b/build.gradle index bca4ae0..405ac1f 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,4 @@ -apply plugin: 'java' -apply plugin: 'eclipse' -apply plugin: 'idea' -apply plugin: 'maven' +apply plugin: 'java-library' group = 'com.github.movisens' @@ -15,7 +12,7 @@ repositories { } dependencies { - testCompile group: 'junit', name: 'junit', version: '4.11' + testImplementation group: 'junit', name: 'junit', version: '4.11' } sourceSets { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 6c0fd3c..2d70ce2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/settings.gradle b/settings.gradle index c7f54e8..39b7886 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,20 +1,2 @@ -/* - * This settings file was auto generated by the Gradle buildInit task - * by 'Juergen' at '15.03.15 19:57' with Gradle 1.11 - * - * The settings file is used to specify which projects to include in your build. - * In a single project build this file can be empty or even removed. - * - * Detailed information about configuring a multi-project build in Gradle can be found - * in the user guide at http://gradle.org/docs/1.11/userguide/multi_project_builds.html - */ - -/* -// To declare projects as part of a multi-project build use the 'include' method -include 'shared' -include 'api' -include 'services:webservice' -*/ - rootProject.name = 'SmartGattLib' From a3874bb126554a47ffed9101ffd297d0e4605732 Mon Sep 17 00:00:00 2001 From: "ulrich.grossmann" Date: Mon, 18 Jul 2022 16:06:51 +0200 Subject: [PATCH 49/60] compatibility 1.8, added maven-publish --- build.gradle | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 405ac1f..d7eb0ff 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,10 @@ apply plugin: 'java-library' +apply plugin: 'maven-publish' group = 'com.github.movisens' -targetCompatibility = 1.6 -sourceCompatibility = 1.6 +targetCompatibility = 1.8 +sourceCompatibility = 1.8 version = '4.0.0-SNAPSHOT' @@ -33,3 +34,11 @@ jar { ) } } + +publishing { + publications { + maven(MavenPublication) { + from components.java + } + } +} From 8e8de8ebf9ba97ad5ea73aafb33d59cc8551e5fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Mon, 18 Jul 2022 16:17:51 +0200 Subject: [PATCH 50/60] model number string is a plain attribute --- .../movisens/smartgattlib/attributes/ModelNumberString.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java b/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java index 4dbb893..671c0e0 100644 --- a/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java +++ b/src-gen/main/java/com/movisens/smartgattlib/attributes/ModelNumberString.java @@ -4,8 +4,9 @@ import com.movisens.smartgattlib.helper.AbstractReadAttribute; import com.movisens.smartgattlib.helper.Characteristic; import com.movisens.smartgattlib.helper.GattByteBuffer; +import com.movisens.smartgattlib.helper.PlainTextAttribute; -public class ModelNumberString extends AbstractReadAttribute +public class ModelNumberString extends AbstractReadAttribute implements PlainTextAttribute { public static final Characteristic CHARACTERISTIC = Characteristics.MODEL_NUMBER_STRING; From 9f9d23c299bbc953188fde2d75636513b0e395e5 Mon Sep 17 00:00:00 2001 From: "ulrich.grossmann" Date: Thu, 28 Jul 2022 13:43:45 +0200 Subject: [PATCH 51/60] updated gradle wrapper --- gradle/wrapper/gradle-wrapper.jar | Bin 53556 -> 59536 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 282 ++++++++++++++--------- gradlew.bat | 49 ++-- 4 files changed, 201 insertions(+), 133 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index ca78035ef0501d802d4fc55381ef2d5c3ce0ec6e..7454180f2ae8848c63b8b4dea2cb829da983f2fa 100644 GIT binary patch literal 59536 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL literal 53556 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�H$uJM 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 \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -89,76 +140,95 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" - -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 832fdb6..107acd3 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,3 +1,19 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + @if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @@ -13,15 +29,18 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if "%ERRORLEVEL%" == "0" goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,34 +64,14 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell From 3033086deb010e6cfa5a7c06e7914224c1a65e09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Tue, 20 Sep 2022 13:58:46 +0200 Subject: [PATCH 52/60] changed file encoding to utf8 --- build.gradle | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle b/build.gradle index d7eb0ff..cb5c152 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,9 @@ group = 'com.github.movisens' targetCompatibility = 1.8 sourceCompatibility = 1.8 +compileJava.options.encoding = 'UTF-8' +compileTestJava.options.encoding = 'UTF-8' + version = '4.0.0-SNAPSHOT' repositories { From 54f3bfef8ca04e0b39c0881521bb3c2e1ce4b4e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Wed, 21 Sep 2022 13:57:21 +0200 Subject: [PATCH 53/60] updated properties file using dependency analyzer --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index cb5c152..8913b88 100644 --- a/build.gradle +++ b/build.gradle @@ -45,3 +45,7 @@ publishing { } } } + +task writeProperties(){ + +} \ No newline at end of file From c0a75007a62357838b70256b4cca7a21b2964b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Thu, 29 Sep 2022 10:20:16 +0200 Subject: [PATCH 54/60] removed writeProperties task --- build.gradle | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build.gradle b/build.gradle index 8913b88..cb5c152 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,3 @@ publishing { } } } - -task writeProperties(){ - -} \ No newline at end of file From 63218e0945b77740471969d7d25a836ff709db5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Fri, 4 Nov 2022 14:22:31 +0100 Subject: [PATCH 55/60] aes tests --- .../movisens/smartgattlib/security/AesUtil.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/com/movisens/smartgattlib/security/AesUtil.java b/src/main/java/com/movisens/smartgattlib/security/AesUtil.java index b485881..27079ba 100644 --- a/src/main/java/com/movisens/smartgattlib/security/AesUtil.java +++ b/src/main/java/com/movisens/smartgattlib/security/AesUtil.java @@ -1,5 +1,8 @@ package com.movisens.smartgattlib.security; +import sun.security.util.ByteArrays; + +import java.nio.charset.StandardCharsets; import java.security.spec.KeySpec; import java.util.Arrays; @@ -12,6 +15,20 @@ public class AesUtil { + public static void main(String[] args) { + + byte[] keydata = {1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6}; + + SecretKey key = createAesKey(keydata); + byte[] chiperText = encrypt("1234567890123456".getBytes(StandardCharsets.UTF_8), key); + + for(int i=0;i Date: Thu, 10 Nov 2022 09:51:48 +0100 Subject: [PATCH 56/60] removed unused include --- src/main/java/com/movisens/smartgattlib/security/AesUtil.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/movisens/smartgattlib/security/AesUtil.java b/src/main/java/com/movisens/smartgattlib/security/AesUtil.java index 27079ba..c85be72 100644 --- a/src/main/java/com/movisens/smartgattlib/security/AesUtil.java +++ b/src/main/java/com/movisens/smartgattlib/security/AesUtil.java @@ -1,7 +1,5 @@ package com.movisens.smartgattlib.security; -import sun.security.util.ByteArrays; - import java.nio.charset.StandardCharsets; import java.security.spec.KeySpec; import java.util.Arrays; From b814f52c715078420ed32b3c8106cfe33764d3ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Thu, 23 Nov 2023 09:27:41 +0100 Subject: [PATCH 57/60] updated changelog --- CHANGELOG.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc60e35..144a637 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,9 @@ - -# [4.0.0] + +# [3.6.0] -- allowed encryption of over the air data -- the method getBytes of AbstractAttribute was renamed to getOutgoingData and needs CryptoManager as argument -- changed signature Characteristic.createAttribute: CryptoManager was added +- added application level encryption for over the air data +- added method getOutgoingData to AbstractAttribute that replaces the method getBytes and needs CryptoManager as argument +- added alternative signature for Characteristic.createAttribute: added new parameter for CryptoManager - to get the raw data representation of an attribute the method getRawData was added to AbstractAttribute From 8f8a430e44a6ab9585b2348639f3b64650349e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Thu, 23 Nov 2023 09:28:22 +0100 Subject: [PATCH 58/60] added backward compatibility to version 3.5.0 --- .../smartgattlib/helper/AbstractAttribute.java | 7 ++++++- .../smartgattlib/helper/Characteristic.java | 13 +++++++++++++ src/test/java/WeightTest.java | 6 +++--- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java index 1ddfbe4..1ae3073 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java +++ b/src/main/java/com/movisens/smartgattlib/helper/AbstractAttribute.java @@ -7,6 +7,11 @@ public abstract class AbstractAttribute protected byte[] data; + public byte[] getBytes() + { + return data; + } + /** * Get data to be sent via BLE. This data may be encrypted. * @@ -27,7 +32,7 @@ public byte[] getOutgoingData(CryptoManager cryptoManager) /** * Gets the raw data representation of this attribute. This data is not encrypted. * - * @return raw adta representation + * @return raw data representation */ public byte[] getRawData() { diff --git a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java index 4e77d01..d4694b6 100644 --- a/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java +++ b/src/main/java/com/movisens/smartgattlib/helper/Characteristic.java @@ -26,6 +26,19 @@ public Characteristic(String uuid, String name, Class attributeClass, Charact this.requiredCharacteristics = requiredCharacteristics; } + public AbstractAttribute createAttribute(byte[] data) + { + try + { + return attributeClass.getConstructor(byte[].class).newInstance(data); + } + catch (Throwable e) + { + e.printStackTrace(); + return new DefaultAttribute(data); + } + } + public AbstractAttribute createAttribute(CryptoManager cryptoManager, byte[] incommingData) { try diff --git a/src/test/java/WeightTest.java b/src/test/java/WeightTest.java index 604ee91..1ad9644 100644 --- a/src/test/java/WeightTest.java +++ b/src/test/java/WeightTest.java @@ -19,8 +19,8 @@ public void testWeight() { assertEquals(weight.getWeight(), weightFloat, 0); assertEquals(weightBytes.getWeight(), weightFloat.doubleValue(), 0); assertEquals(weightBytes.getWeight(), weight.getWeight()); - assertArrayEquals(weight.getRawData(), bytes); - assertArrayEquals(weightBytes.getRawData(), bytes); - assertArrayEquals(weight.getRawData(), weightBytes.getRawData()); + assertArrayEquals(weight.getBytes(), bytes); + assertArrayEquals(weightBytes.getBytes(), bytes); + assertArrayEquals(weight.getBytes(), weightBytes.getBytes()); } } From 1204294fd6312cb53e929524e09882323b2d1fee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Thu, 23 Nov 2023 09:29:17 +0100 Subject: [PATCH 59/60] updated version to 3.6.0 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index cb5c152..cb016cb 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ sourceCompatibility = 1.8 compileJava.options.encoding = 'UTF-8' compileTestJava.options.encoding = 'UTF-8' -version = '4.0.0-SNAPSHOT' +version = '3.6.0' repositories { mavenCentral() From 377e8ec3ce6449b0d180ea2893ddf57b0c07be77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ulrich=20Gro=C3=9Fmann?= Date: Thu, 23 Nov 2023 09:30:19 +0100 Subject: [PATCH 60/60] updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f546c86..a35d88d 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Example Android project with SmartGattLib available [here](https://github.com/mo maven { url "https://jitpack.io/" } } dependencies { - compile 'com.github.movisens:SmartGattLib:3.0' + compile 'com.github.movisens:SmartGattLib:3.6' } ``` or download the latest .jar file from the [releases](https://github.com/movisens/SmartGattLib/releases) page and place it in your Android app’s libs/ folder.