How to decode a Bluetooth LE Package / Frame / Beacon of a FreeTec PX-1737-919 Bluetooth 4.0 Temperature Sensor?
Asked Answered
C

4

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
Cornwallis answered 7/9, 2014 at 19:34 Comment(2)
Where did those byte sequences come from? Why do you think it should look like an iBeacon transmission?Valtin
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.Diggings
V
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

Valtin answered 7/9, 2014 at 23:19 Comment(4)
Could you explain me, what the first 7 bytes 04 3E 26 02 01 03 01 are?Diggings
Ok, the third byte 26is 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 01before the Bluetooth Mac Address? Some fixed Mac Address identifier?Diggings
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.Diggings
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?Unreel
C
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.

Caius answered 15/3, 2016 at 21:30 Comment(1)
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.Unreel
M
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.

Million answered 30/8, 2016 at 1:15 Comment(0)
C
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.

Capernaum answered 6/2, 2021 at 9:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.