From 31506f47638fb3203ed68cffdd0c1f063a2a37ab Mon Sep 17 00:00:00 2001 From: Vlad Kolotov Date: Tue, 13 Nov 2018 20:24:16 +1300 Subject: [PATCH] fixes #10 (Add support for 128 bit service and characteristic UUIDs) --- .../gattparser/BluetoothGattParser.java | 39 +++++++++++-------- .../BluetoothGattSpecificationReader.java | 10 ++++- ...haracteristic.temperature_and_humidity.xml | 2 +- ...tific.service.temperature_and_humidity.xml | 2 +- ...icCharacteristicParserIntegrationTest.java | 27 +++++++++++-- ...0li.bluetooth.characteristic.rswitchv1.xml | 18 +++++++++ 6 files changed, 75 insertions(+), 23 deletions(-) create mode 100644 src/test/resources/gatt/characteristic/com.r00li.bluetooth.characteristic.rswitchv1.xml diff --git a/src/main/java/org/sputnikdev/bluetooth/gattparser/BluetoothGattParser.java b/src/main/java/org/sputnikdev/bluetooth/gattparser/BluetoothGattParser.java index e315aef..127255d 100644 --- a/src/main/java/org/sputnikdev/bluetooth/gattparser/BluetoothGattParser.java +++ b/src/main/java/org/sputnikdev/bluetooth/gattparser/BluetoothGattParser.java @@ -66,6 +66,8 @@ */ public class BluetoothGattParser { + private final static String BASE_UUID = "-0000-1000-8000-00805F9B34FB"; + private final Logger logger = LoggerFactory.getLogger(GenericCharacteristicParser.class); private BluetoothGattSpecificationReader specificationReader; @@ -83,7 +85,7 @@ public class BluetoothGattParser { * @return true if the parser has loaded definitions for that characteristic, false otherwise */ public boolean isKnownCharacteristic(String characteristicUUID) { - return specificationReader.getCharacteristicByUUID(getShortUUID(characteristicUUID)) != null; + return specificationReader.getCharacteristicByUUID(trim(characteristicUUID)) != null; } /** @@ -92,7 +94,7 @@ public boolean isKnownCharacteristic(String characteristicUUID) { * @return true if the parser has loaded definitions for that service, false otherwise */ public boolean isKnownService(String serviceUUID) { - return specificationReader.getService(getShortUUID(serviceUUID)) != null; + return specificationReader.getService(trim(serviceUUID)) != null; } /** @@ -118,7 +120,7 @@ public GattResponse parse(String characteristicUUID, byte[] raw) throws Characte * @return list of fields represented by {@link GattRequest} for a write operation */ public GattRequest prepare(String characteristicUUID) { - characteristicUUID = getShortUUID(characteristicUUID); + characteristicUUID = trim(characteristicUUID); return new GattRequest(characteristicUUID, specificationReader.getFields(specificationReader.getCharacteristicByUUID(characteristicUUID))); } @@ -135,7 +137,7 @@ public GattRequest prepare(String characteristicUUID) { * @return list of fields represented by {@link GattRequest} for a write operation */ public GattRequest prepare(String characteristicUUID, byte[] initial) { - characteristicUUID = getShortUUID(characteristicUUID); + characteristicUUID = trim(characteristicUUID); return new GattRequest(characteristicUUID, parseFields(characteristicUUID, initial)); } @@ -170,7 +172,7 @@ public byte[] serialize(GattRequest gattRequest, boolean strict) { throw new IllegalArgumentException("GATT request is not valid"); } synchronized (customParsers) { - String characteristicUUID = getShortUUID(gattRequest.getCharacteristicUUID()); + String characteristicUUID = trim(gattRequest.getCharacteristicUUID()); if (strict && !isValidForWrite(characteristicUUID)) { throw new CharacteristicFormatException( "Characteristic is not valid for write: " + characteristicUUID); @@ -188,7 +190,7 @@ public byte[] serialize(GattRequest gattRequest, boolean strict) { * @return a GATT service specification by its UUID */ public Service getService(String serviceUUID) { - return specificationReader.getService(getShortUUID(serviceUUID)); + return specificationReader.getService(trim(serviceUUID)); } /** @@ -197,7 +199,7 @@ public Service getService(String serviceUUID) { * @return a GATT characteristic specification by its UUID */ public Characteristic getCharacteristic(String characteristicUUID) { - return specificationReader.getCharacteristicByUUID(getShortUUID(characteristicUUID)); + return specificationReader.getCharacteristicByUUID(trim(characteristicUUID)); } /** @@ -209,7 +211,7 @@ public Characteristic getCharacteristic(String characteristicUUID) { * @return a list of field specifications for a given characteristic */ public List getFields(String characteristicUUID) { - return specificationReader.getFields(getCharacteristic(getShortUUID(characteristicUUID))); + return specificationReader.getFields(getCharacteristic(trim(characteristicUUID))); } /** @@ -219,7 +221,7 @@ public List getFields(String characteristicUUID) { */ public void registerParser(String characteristicUUID, CharacteristicParser parser) { synchronized (customParsers) { - customParsers.put(getShortUUID(characteristicUUID), parser); + customParsers.put(trim(characteristicUUID), parser); } } @@ -233,7 +235,7 @@ public void registerParser(String characteristicUUID, CharacteristicParser parse * @return true if a given characteristic is valid for read operation */ public boolean isValidForRead(String characteristicUUID) { - Characteristic characteristic = specificationReader.getCharacteristicByUUID(getShortUUID(characteristicUUID)); + Characteristic characteristic = specificationReader.getCharacteristicByUUID(trim(characteristicUUID)); return characteristic != null && characteristic.isValidForRead(); } @@ -247,7 +249,7 @@ public boolean isValidForRead(String characteristicUUID) { * @return true if a given characteristic is valid for write operation */ public boolean isValidForWrite(String characteristicUUID) { - Characteristic characteristic = specificationReader.getCharacteristicByUUID(getShortUUID(characteristicUUID)); + Characteristic characteristic = specificationReader.getCharacteristicByUUID(trim(characteristicUUID)); return characteristic != null && characteristic.isValidForWrite(); } @@ -346,15 +348,20 @@ public byte[] serialize(String raw, int radix) { return bytes; } - private String getShortUUID(String uuid) { - if (uuid.length() < 8) { - return uuid.toUpperCase(); + private String trim(String uuid) { + if (uuid.endsWith(BASE_UUID)) { + String shortUUID = uuid.substring(0, 8).toUpperCase(); + if (shortUUID.startsWith("0000")) { + return shortUUID.substring(4, 8); + } else { + return shortUUID; + } } - return Long.toHexString(Long.valueOf(uuid.substring(0, 8), 16)).toUpperCase(); + return uuid.toUpperCase(); } private LinkedHashMap parseFields(String characteristicUUID, byte[] raw) { - characteristicUUID = getShortUUID(characteristicUUID); + characteristicUUID = trim(characteristicUUID); synchronized (customParsers) { if (!isValidForRead(characteristicUUID)) { throw new CharacteristicFormatException("Characteristic is not valid for read: " + characteristicUUID); diff --git a/src/main/java/org/sputnikdev/bluetooth/gattparser/spec/BluetoothGattSpecificationReader.java b/src/main/java/org/sputnikdev/bluetooth/gattparser/spec/BluetoothGattSpecificationReader.java index e916fbc..194d6d4 100644 --- a/src/main/java/org/sputnikdev/bluetooth/gattparser/spec/BluetoothGattSpecificationReader.java +++ b/src/main/java/org/sputnikdev/bluetooth/gattparser/spec/BluetoothGattSpecificationReader.java @@ -220,6 +220,14 @@ public void loadExtensionsFromFolder(String path) { readCharacteristics(getFilesFromFolder(characteristicsFolderName)); } + public void addCharacteristic(URL url) { + logger.info("Adding a characteristic: {}", url); + Characteristic characteristic = getCharacteristic(url); + if (characteristic != null) { + addCharacteristic(characteristic); + } + } + private static URL getSpecResourceURL(URL catalogURL, String characteristicType) throws MalformedURLException { String catalogFilePath = catalogURL.getFile(); int lastSlashPos = catalogFilePath.lastIndexOf('/'); @@ -368,7 +376,7 @@ private Characteristic loadCharacteristic(String uuid) { return getCharacteristic(url); } - private void readServices(List files) { + void readServices(List files) { for (URL file : files) { Service service = getService(file); if (service != null) { diff --git a/src/main/resources/gatt/characteristic/com.oregonscientific.characteristic.temperature_and_humidity.xml b/src/main/resources/gatt/characteristic/com.oregonscientific.characteristic.temperature_and_humidity.xml index 46cfa00..6b59ed5 100644 --- a/src/main/resources/gatt/characteristic/com.oregonscientific.characteristic.temperature_and_humidity.xml +++ b/src/main/resources/gatt/characteristic/com.oregonscientific.characteristic.temperature_and_humidity.xml @@ -1,7 +1,7 @@ diff --git a/src/main/resources/gatt/service/com.oregonscientific.service.temperature_and_humidity.xml b/src/main/resources/gatt/service/com.oregonscientific.service.temperature_and_humidity.xml index a2c9b0b..754421d 100644 --- a/src/main/resources/gatt/service/com.oregonscientific.service.temperature_and_humidity.xml +++ b/src/main/resources/gatt/service/com.oregonscientific.service.temperature_and_humidity.xml @@ -1,6 +1,6 @@ + type="com.oregonscientific.service.temperature_and_humidity" uuid="74E7FE00-C6A4-11E2-B7A9-0002A5D5C51B" last-modified="2018-10-10"> Oregon Scientific BLE Weather Station temperature and humidity service diff --git a/src/test/java/org/sputnikdev/bluetooth/gattparser/GenericCharacteristicParserIntegrationTest.java b/src/test/java/org/sputnikdev/bluetooth/gattparser/GenericCharacteristicParserIntegrationTest.java index b509d9c..fd05ab2 100644 --- a/src/test/java/org/sputnikdev/bluetooth/gattparser/GenericCharacteristicParserIntegrationTest.java +++ b/src/test/java/org/sputnikdev/bluetooth/gattparser/GenericCharacteristicParserIntegrationTest.java @@ -21,9 +21,12 @@ */ import org.junit.Test; +import org.sputnikdev.bluetooth.gattparser.spec.BluetoothGattSpecificationReader; import org.sputnikdev.bluetooth.gattparser.spec.Enumeration; import java.math.BigInteger; +import java.net.MalformedURLException; +import java.net.URL; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -146,10 +149,10 @@ public void testOregonWeatherStation() { byte[] actual = {0x01, 0x0d, 0x01, (byte) 0xec, 0x00, (byte) 0xff, 0x7f, (byte) 0xff, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, (byte) 0xff, (byte) 0xff, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f}; byte[] minMax = {(byte) 130, 0x7f, 0x7f, 0x7f, 0x21, 0x01, (byte) 0xf8, 0x00, 0x33, 0x01, (byte) 0xba, 0x00, (byte) 0xff, 0x7f, (byte) 0xff, 0x7f, (byte) 0xff, 0x7f, (byte) 0xff, 0x7f}; - assertTrue(parser.isKnownService("74E7FE00")); - assertTrue(parser.isKnownCharacteristic("74E78E10")); + assertTrue(parser.isKnownService("74E7FE00-C6A4-11E2-B7A9-0002A5D5C51B")); + assertTrue(parser.isKnownCharacteristic("74E78E10-C6A4-11E2-B7A9-0002A5D5C51B")); - GattResponse response1 = parser.parse("74E78E10", actual); + GattResponse response1 = parser.parse("74E78E10-C6A4-11E2-B7A9-0002A5D5C51B", actual); assertEquals(26.9, response1.get("Base temp").getDouble(), 0.1); assertEquals(23.6, response1.get("Sensor 1 temp").getDouble(), 0.1); assertEquals(3276.7, response1.get("Sensor 2 temp").getDouble(), 0.1); @@ -167,7 +170,7 @@ public void testOregonWeatherStation() { assertEquals(127, (int) response1.get("Sensor 2 humidity max").getInteger()); - GattResponse response2 = parser.parse("74E78E10", minMax); + GattResponse response2 = parser.parse("74E78E10-C6A4-11E2-B7A9-0002A5D5C51B", minMax); assertEquals(127, (int) response2.get("Sensor 2 humidity min").getInteger()); assertEquals(127, (int) response2.get("Sensor 3 humidity max").getInteger()); assertEquals(127, (int) response2.get("Sensor 3 humidity min").getInteger()); @@ -419,6 +422,22 @@ public void testMinewKeyfinderGetAuthStatusCode() { assertArrayEquals(expected, parser.serialize(request, false)); } + @Test + public void testRooliSwitch() throws MalformedURLException { + ClassLoader classLoader = getClass().getClassLoader(); + BluetoothGattParserFactory.getSpecificationReader() + .addCharacteristic(classLoader.getResource("gatt/characteristic/com.r00li.bluetooth.characteristic.rswitchv1.xml")); + + assertTrue(parser.isKnownCharacteristic("9ED90C00-71D7-11E5-977A-0002A5D5C51B")); + + GattRequest request = parser.prepare("9ED90C00-71D7-11E5-977A-0002A5D5C51B"); + request.setField("Switch state", 255); + + byte[] raw = parser.serialize(request); + assertEquals(1, raw.length); + assertEquals("11111111", Integer.toBinaryString(Byte.toUnsignedInt(raw[0]))); + } + private void assertField(Integer expectedValue, String expectedEnum, String characteristicUUID, byte[] data, String fieldName) { FieldHolder fieldHolder = parser.parse(characteristicUUID, data).get(fieldName); diff --git a/src/test/resources/gatt/characteristic/com.r00li.bluetooth.characteristic.rswitchv1.xml b/src/test/resources/gatt/characteristic/com.r00li.bluetooth.characteristic.rswitchv1.xml new file mode 100644 index 0000000..7e72574 --- /dev/null +++ b/src/test/resources/gatt/characteristic/com.r00li.bluetooth.characteristic.rswitchv1.xml @@ -0,0 +1,18 @@ + + + + + + Mandatory + 8bit + + + + + + + + \ No newline at end of file