6

The Sensor advertises these Bluetooth LE Packages:

> 04 3E 26 02 01 03 01 B8 AB C0 5D 4C D9 1A 02 01 04 09 09 38 
  42 42 41 43 34 39 44 07 16 09 18 47 08 00 FE 04 16 0F 18 5B 
  B3 
> 04 3E 26 02 01 03 01 B8 AB C0 5D 4C D9 1A 02 01 04 09 09 38 
  42 42 41 43 34 39 44 07 16 09 18 45 08 00 FE 04 16 0F 18 5A 
  BC 
> 04 3E 26 02 01 03 01 B8 AB C0 5D 4C D9 1A 02 01 04 09 09 38 
  42 42 41 43 34 39 44 07 16 09 18 44 08 00 FE 04 16 0F 18 5B 
  B2 

How do I decode it?

LE Advertising Report:

  ADV_NONCONN_IND - Non connectable undirected advertising (3)
  bdaddr D9:4C:5D:C0:AB:B8 (Random)
  Flags: 0x04
  Complete local name: '8BBAC49D'
  Unknown type 0x16 with 6 bytes data
  Unknown type 0x16 with 3 bytes data
  RSSI: -77
Youssif Saeed
  • 11,789
  • 4
  • 44
  • 72
André Fiedler
  • 1,093
  • 4
  • 16
  • 25
  • Where did those byte sequences come from? Why do you think it should look like an iBeacon transmission? – davidgyoung Sep 07 '14 at 21:08
  • I don't really know. It's a Broadcast from an Bluetooth 4.0 Low Energy Sensor. And it start's with `04 3E` so I thought it could be some modified I iBeacon. And it's designed to work with iOS. But I don't really know if it's a iBeacon. I want to read out the current temperature, but don't know how to parse it. – André Fiedler Sep 07 '14 at 21:34

4 Answers4

13

It's not a beacon advertisement. The packets are the device sending three pieces of information.

  • The device's local name "8BBAC49D"
  • The Health Thermometer Service is available (with a current temperature measurement)
  • The Battery Service is available (with a current battery level measurement)

Breakdown of this BLE discovered packet:

> 04 3E 26 02 01 03 01 B8 AB C0 5D 4C D9 1A 02 01 04 09 09 38 
  42 42 41 43 34 39 44 07 16 09 18 44 08 00 FE 04 16 0F 18 5B 
  B2 

If you look at your repeat packet, you will see that each temperature measurement varies slightly, as does the battery measurement.

Here is the breakdown of the packet:

B8 AB C0 5D 4C D9 1A # Bluetooth Mac Address
02 # Number of bytes that follow in first AD structure
01 # Flags AD type
04 # Flags value 0x04 = 000000100  
   bit 0 (OFF) LE Limited Discoverable Mode
   bit 1 (OFF) LE General Discoverable Mode
   bit 2 (ON) BR/EDR Not Supported
   bit 3 (OFF) Simultaneous LE and BR/EDR to Same Device Capable (controller)
   bit 4 (OFF) Simultaneous LE and BR/EDR to Same Device Capable (Host)
09 # Number of bytes that follow in the first AD Structure
09 # Complete Local Name AD Type
38 42 42 41 43 34 39 44 # "8BBAC49D"
07 # Number of bytes that follow in the second AD Structure
16 # Service Data AD Type
09 18 # 16-bit Service UUID 0x1809 = Health thermometer (org.bluetooth.service.health_thermometer)
44 08 00 FE # Additional Service Data 440800  (Temperature = 0x000844 x 10^-2) = 21.16 degrees
04 # Number of bytes that follow in the third AD Structure
16 # Service Data AD Type
0F 18 # 16-bit Service UUID 0x180F  = Battery Service (org.bluetooth.service.battery_service) 
5B # Additional Service Data (battery level)
B2 # checksum

See the bluetooth 16-bit service UUID definitions for more information:

https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.battery_service.xml

https://developer.bluetooth.org/gatt/services/Pages/ServiceViewer.aspx?u=org.bluetooth.service.health_thermometer.xml

André Fiedler
  • 1,093
  • 4
  • 16
  • 25
davidgyoung
  • 63,876
  • 14
  • 121
  • 204
  • 1
    Could you explain me, what the first 7 bytes `04 3E 26 02 01 03 01` are? – André Fiedler Sep 08 '14 at 07:19
  • Ok, the third byte `26`is int 38 and should be the total data/package length you broke down above. What are the first two bytes `04 3E`? Some manufacture info? And what are the 4 bytes `02 01 03 01`before the Bluetooth Mac Address? Some fixed Mac Address identifier? – André Fiedler Sep 08 '14 at 07:56
  • Hm, the first byte `04` could be the Generic Access Profile and the second byte `3E` could be the Company Identifier (Systems and Chips, Inc.). But this is just a guess. I can't find any specification how such a BLE package is build. – André Fiedler Sep 09 '14 at 15:02
  • The last byte doesn't feel like a CRC but as a RSSI --its values are in the same ballpark and seems strange that the three examples by OP are so close in value. I read somewhere else that "last byte is the RSSI" but I am not sure if I am lookint to the same thing... The developer bluetooth links are now broken. Do you happen to know where should I reach for further information? – MariusSiuram Aug 16 '20 at 17:10
9

You can use hcidump -w dump.log to record some packages and open it in Wireshark - which does most of the decoding for you.

The missing pices:

04 # HCI Packet Type: HCI Event (0x04)
3E # Event Code: LE Meta (0x3e)
26 # Parameter Total Length: 38
02 # Sub Event: LE Advertising Report (0x02)
01 # Num Reports: 1
03 # Event Type: Non-Connectable Undirected Advertising (0x03)
01 # Peer Address Type: Random Device Address (0x01)

Screenshot form Wireshark: Wireshark

And here is the Packet in btsnoop.log format. Works with Wireshark and hcidump -r packet.log.

Florian F
  • 4,044
  • 3
  • 30
  • 31
  • `hcidump -w dump.log` I don't know what I'm missing, but in my Raspberry Pi this command just logs `btsnoop` and nothing else. It feels as if it was not capturing LE packets? Or should I do something else before that? `hcitool lescan` spews stuff, and that's what I wanted to wireshark. – MariusSiuram Aug 16 '20 at 17:17
1
  public class Util {

public static int convertU16ToInt(byte i) {
    int firstByte = (0x000000FF & ((int)i));
    return firstByte;
}

public static int bytesToInt(final byte[] array, final int start)
{
    final ByteBuffer buf = ByteBuffer.wrap(array); // big endian by default
    buf.position(start);
    buf.put(array);
    buf.position(start);
    return buf.getInt();
}

public static int convertU32ToInt(byte b[], int start) {
    return ((b[start] << 24) & 0xff000000 |(b[start + 1] << 16) & 0xff0000
            | (b[start + 2] << 8) & 0xff00 | (b[start + 3]) & 0xff);
}
public static long int64Converter(byte buf[], int start) {
    return ((buf[start] & 0xFFL) << 56) | ((buf[start + 1] & 0xFFL) << 48)
            | ((buf[start + 2] & 0xFFL) << 40)
            | ((buf[start + 3] & 0xFFL) << 32)
            | ((buf[start + 4] & 0xFFL) << 24)
            | ((buf[start + 5] & 0xFFL) << 16)
            | ((buf[start + 6] & 0xFFL) << 8)
            | ((buf[start + 7] & 0xFFL) << 0);
}

public static long convertU16ToInt(byte[] buf, int index) {

    int firstByte  = (0x000000FF & ((int)buf[index]));
    int secondByte = (0x000000FF & ((int)buf[index+1]));
    int thirdByte  = (0x000000FF & ((int)buf[index+2]));
    int fourthByte = (0x000000FF & ((int)buf[index+3]));

    index = index+4;

    long anUnsignedInt  = ((long) (firstByte << 24
            | secondByte << 16
            | thirdByte << 8
            | fourthByte))
            & 0xFFFFFFFFL;

    return anUnsignedInt;
}

public static short toUnsigned(byte b) {
    return (short)(b & 0xff);
}


public static int convertU16ToInt(byte byte1, byte byte2) {
    int N = (( 255 - byte1 & 0xff )  << 8 ) | byte2 & 0xff;
    return N;
}

public static short UInt16Decode(byte inbyByteA, byte inbyByteB) {
    short n =  (short)(((inbyByteA & 0xFF) << 8) | (inbyByteB & 0xFF));
    return n;
}


public static long UInt32Decode(int inbyByteA, int inbyByteB) {
    int n = inbyByteA<< 16 | inbyByteB;

    return n;
}


public static long decodeMeasurement16(byte byte3, byte byte4) {
    return 0L;
}

public static double decodeMeasurement32(byte byte3, byte byte4, byte byte6, byte byte7) {

    double outdblFloatValue = 0;
    int outi16DecimalPointPosition = 0;

    int ui16Integer1 = convertU16ToInt (byte3, byte4);
    int ui16Integer2 = convertU16ToInt (byte6, byte7);

    int ui32Integer = ( (int)UInt32Decode (ui16Integer1, ui16Integer2) ) & 0x07FFFFFF;

    outi16DecimalPointPosition = ((0x000000FF - byte3 ) >> 3) - 15;

    // Decode raw value, with Exampledata: 0x05FFFFFC
    if ((100000000 + 0x2000000) > ui32Integer) {
        // Data is a valid value
        if (0x04000000 == (ui32Integer & 0x04000000)) {
            ui32Integer = (ui32Integer | 0xF8000000);
            // With Exampledata: 0xFDFFFFFC
        }
        ui32Integer = ui32Integer + 0x02000000; // with Exampledata: 0xFFFFFFFC
    }
    else {
        // Data contains error code, decode error code
        outdblFloatValue = (double)((ui32Integer - 0x02000000) - 16352.0);
        outi16DecimalPointPosition = 0;
        return -36; // Return value is error code
    }
    outdblFloatValue = (double)ui32Integer;
    outdblFloatValue = outdblFloatValue / (Math.pow(10.0f, (double)outi16DecimalPointPosition));

    return outdblFloatValue;
}

public static int toByte(int number) {
    int tmp = number & 0xff;
    return (tmp & 0x80) == 0 ? tmp : tmp - 256;
}

public static long getUnsignedInt(int x) {
    return x & 0x00000000ffffffffL;
}

}

After getting bytes array, call Util.decodeMeasurement32(byte[9], byte[10], byte[11], byte[12]). These bytes are the temperature bytes.

0

The thermometer works like a BLE beacon, i.e. you cannot connect. All information is given in the advertising every 5 seconds or so. E.g. with Android & the blessed library (https://github.com/weliem/blessed-android):

    lateinit var central: BluetoothCentralManager

    val HTS_SERVICE_UUID = ParcelUuid.fromString("00001809-0000-1000-8000-00805f9b34fb")
    val BTS_SERVICE_UUID = ParcelUuid.fromString("0000180f-0000-1000-8000-00805f9b34fb")
    
    fun convertTemperature(bytes: ByteArray?): Float {
        if (null == bytes)
            return -273.15f
        val bb: ByteBuffer = ByteBuffer.allocate(2)
        bb.order(ByteOrder.LITTLE_ENDIAN)
        bb.put(bytes[0])
        bb.put(bytes[1])
        return bb.getShort(0) / 100.0f
    }

    val bluetoothCentralManagerCallback: BluetoothCentralManagerCallback = object : BluetoothCentralManagerCallback() {
        override fun onDiscoveredPeripheral(peripheral: BluetoothPeripheral, scanResult: ScanResult) {
            val rssi = scanResult.rssi
            val scanRec = scanResult.scanRecord
            val payload = scanRec?.serviceData
            val temperature = convertTemperature(payload?.get(key = HTS_SERVICE_UUID))
            val other_data = payload?.get(key = HTS_SERVICE_UUID)?.sliceArray(2..3)
            val battery_level = payload?.get(key = BTS_SERVICE_UUID)
            central.stopScan()

            tempLabel.text = getString(R.string.temp, temperature)
        }
    }

    central = BluetoothCentralManager(applicationContext, bluetoothCentralManagerCallback, Handler(Looper.getMainLooper()))
    central.scanForPeripheralsWithNames(arrayOf("7FE2183D"))

Every time you want a new reading you can start scanning again.

ergo
  • 23
  • 3