Bluetooth SIG Battery Service Implementation

Posted on
  • Hello, I'm trying to implement the Bluetooth SIG Battery Service but it's very complicated and there's absolutely no help in terms of example available online. I'm not trying to implement all of the characteristics in that service only battery_level & battery_level_status. Implementing is very confusing because the documentation provided by bluetooth doesn't match any of the implementations you can observe on github. Even in NRF's own implementation of the specification the battery service only has two characteristics as opposed to the 5 provided by the specification. Also apparently you're supposed to combine the unit for a value with the actual value somehow which I don't understand and I can't really find any references for it in the specification.

    Can someone please look over my implementation if it follows the specification correctly. For reference I am using Assigned Numbers Revision Date: 20230313 & GATT_Specification_Supplement_v8-1 as reference. I really appreciate the help, thank you in advance.

    let IS_CHARGING = false;
    // Globals
    let IS_CONNECTED;
    
    function onConnect() {
        IS_CONNECTED = true
    }
    
    function onDisconnect() {
        IS_CONNECTED = false
    }
    
    function onCharge(charging) {
        IS_CHARGING = charging
    }
    
    function build_battery_status(){
        const battery_level = E.getBattery()
        // 0 good, 1 bad
        let is_charge_good = battery_level > 80 ? true: false
        const battery_status_flags = 2;
        let power_state_flags = 1
        if(IS_CHARGING){
            // Charging
            power_state_flags = power_state_flags | 32
        }else{
            // Active Discharging
            power_state_flags = power_state_flags | 64
        }
        if(is_charge_good){
            // Good
            power_state_flags = power_state_flags | 128
        }else{
            // Bad
            power_state_flags = power_state_flags | 256
        }
        // I know this part is technically incorrect because battery_status_flags & 
        // battery_level should be 1 octet but since I don't know I don't understand 
        // the endianness(I know it's little endian) I have referained from encoding
        // it directly
        const encoded = new Uint16Array([battery_status_flags, power_state_flags, battery_level]).buffer
        return encoded
    }
    
    function update_battery_status(){
        NRF.updateServices({
            0x180F: {
                0x2A19: {
                    value: [E.getBattery()]
                },
                0x2BED: {
                    value:build_battery_status()
                },
            },
        })
    }
    
    function onInit() {
        Bangle.on('charging', onCharge);
        NRF.on('connect', onConnect);
        NRF.on('disconnect', onDisconnect);
        NRF.setServices({
            // Battery level service
            0x180F: {
                0x2A19: {
                    notify: true,
                    readable: true,
                    indicate: true,
                    description: "Battery Level",
                    value: [E.getBattery()]
                },
                0x2BED: {
                    notify: true,
                    readable: true,
                    indicate: true,
                    description: "Battery Status",
                    value:build_battery_status()
                },
            }
        })
    
        setInterval(update_battery_status, 40_000)
    }
    
  • Well, I can't find the info - googling GATT_Specification_Supplement_v8-1 doesn't return anything, so I can't really confirm if it's right or not, but as far as I can see what you're doing looks good.

    However the use of Uint16Array in build_battery_status does look a bit suspect, as that's going to create 6 bytes of data (when I assume that wasn't what was needed).

    You might need:

    new Uint8Array([battery_status_flags, power_state_flags>>8, power_state_flags&255, battery_level]).buffer
    

    Which would give you 4 bytes (with power_state_flags as little endian 16 bit) - but again I'm not sure what's required so I'm just guessing.

  • I think they change it all the time, the supplement with data structures of characteristics are here https://www.bluetooth.com/specifications­/assigned-numbers/ on same page together with all those service UUIDs

  • Oops sorry my bad, it's this GATT_Specification_Supplement_v8 the -1 was appended by windows I guess it's stopped using (1) to indicate duplicate files. I've added screenshots from the specification directly. Do you know why the specification is so difficult to implement, seems like it's raising the barrier of entry for no reason. Specially since they provide basically no information on how to actually implement it. Do you think you have any reference implementations or blogs that can help.


    3 Attachments

    • Screenshot 2023-03-15 183736.png
    • Screenshot 2023-03-15 183758.png
    • Screenshot 2023-03-15 183824.png
  • I've seen all of that, it's not very helpful for someone at my level. The GATT specification is slightly more helpful because it atleast contains the format for the payload.

  • And what exactly is the difficult part? looks like just bit shifting values you care for to right positions - basically a one liner for the power state, something like

    battPresent|wiredSource<<1|wirelesSource­<<3|chargeState<<5|evel<<7|....

    GATT is little endian so no further conversion needed for the value itself. Then putting it to byte array like Gordon mentioned.

    The reason might be saving power, every byte sent/received takes some power. also bit shifting is really basic thing not really so difficult. And also when implemented in C you can define bitfield directly as part of structure so bits go to right places automatically.

    Also the first Flags byte makes most of it optional so just reporting the level is two bytes
    new Uint8Array([1<<1,level]) and that's it.

    btw I'd probably remove "indicate: true," unless the specs make this required - indications need acknowledgement from the client for every notification sent, just plain notifications should be good enough for battery reporting. well unless that flag means that client can optionally subscribe to indications but also only for plain notifications. Not sure how it works.

  •     new Uint8Array([battery_status_flags, power_state_flags>>8, power_state_flags&255, battery_level]).buffer
    

    Are you sure this is correct? It seems like you're encoding it in big endian, wouldn't the correct encoding be

        new Uint8Array([battery_status_flags, power_state_flags&0xff, power_state_flags>>8, battery_level]).buffer
    
  • Ahh, you're right, yes

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Bluetooth SIG Battery Service Implementation

Posted by Avatar for Espruino🥵Espressif🥵 @Espruino🥵Espressif🥵

Actions