• I've been working on a little project to emulate a BLE footpod on a Pixl.js and I've been using a Milestone Pod (now Zwift) to compare against. The Pixl.js works fine as a Running Speed and Cadence device (0x1814) with the Zwift App on Android but my Garmin watch refuses to recognise it as a footpod.

    I had been trying to clone the Milestone's output just to see which characteristic/setting/value I'm missing but with no success. Yesterday however I found some dead-simple Arduino code for ESP32 doing the same thing as me. It worked instantly with the Garmin and it has none of the extra complexity of the Milestone's output.

    The only major difference I can see between my Espruino output and the Arduino output (in NRF Connect) is that on Espruino the 0x1801 Generic Attribute Service shows as empty, whereas it has 0x2A05 Service Changed Characteristic and 0x2902 Client Characteristic Configuration on ESP32 Arduino.

    I've tried doing:

    0x1801: { // generic attribute service
                0x2a05: {
                    indicate: true,

    in setServices but it has no effect and the Service still shows as empty.

    Is there any way to enable this directly in Espruino or is it one of those things that the underlying stack turns on in certain situations?

    2 Attachments

    • arduino.png
    • espruino.png
  • I think it could be something internal to the Bluetooth stack. I guess you could see if adding a value to it made the characteristic appear?

    The thing is Service Changed really shouldn't be needed. The only place I have ever seen it used is for device firmware updates, so it definitely wouldn't be anything the Garmin watch should need.

    Other things I guess could be an issue:

    • Connection interval - I'm not sure if you can see what connection interval the real footpod reports but it may be you can use NRF.setConnectionInterval to change Espruinos - even if it's only to make the maximum interval higher (the Garmin may well want to use a larger interval to lessen power consumption).
    • Address - the actual Mac addresses look very different. I guess it could be the Garmin refuses to connect to a 'random' address device and a requires Public address: http://www.espruino.com/Reference#l_NRF_­setAddress
    • Pairing - it might be the Garmin needs to pair? Something like this may help http://www.espruino.com/BLE+Security#pas­skey-pin-pairing
  • Thanks @Gordon - I've been on a process of elimination and Service Changed was the last major obvious difference to me.

    I had been wondering about Connection interval.

    I'll try setting the address to Public.

    Pretty sure it isn't pairing at that point, since the Garmin never even lists the Espruino when I ask it to scan. But it always finds the Arduino device.

  • the Garmin never even lists the Espruino when I ask it to scan

    Ok, well that's a great starting point! So it won't be related to services or connection interval or pairing since it's not even connected at that point. It's got to be a difference in the advertising data.

    Can you post up the advertising data that comes from the footpod - from the scanning tab of nRF connect?

    ... my guess is you may just need to advertise the service 0x1814.

  • No joy with temporarily stealing the Milestone's address or playing with connection interval.

    I'm already advertising 0x1814 and NRF Connect sees the Pixl updating the speed and cadence. There just seems to be something in the output that is different between the Pixl.js and the ESP32/Milestone that the Garmin does not like. And there's unfortunately no way to debug built-in features in the Garmin. Whatever it is, Zwift on Android doesn't care about it and sees the Pixl as a footpod with no issue.

    I went down a rabbit hole with the Milestone Pod as it alternates between advertising as a footpod and advertising as an iBeacon. I thought that was the difference. But the ESP32 Arduino code doesn't do that.

    All three in screenshots. FootpodConor is the ESP32/Arduino device. MilestonePod 59 is the real commercial footpod. Pixl.js 8dfc is the only one that the Garmin doesn't see.

    I think the Garmin is connecting to the devices, if not pairing, as it doesn't list them if they are connected in NRF Connect.

    3 Attachments

    • 2020-12-15 10.30.10.jpg
    • 2020-12-15 10.30.52.jpg
    • 2020-12-15 10.32.51.jpg
  • It'd be really interesting to see what the 'raw' data is from Bluetooth - but I'm pretty sure I know what the problem is.

    You'll be using NRF.setServices({...},{advertise:[0x1814­]}) I guess, or something like that?

    It's come up a few times but Espruino has an annoying quirk - advertised services get put in the scan response packet rather than the main advertising one. So if the Garmin isn't doing 'active' scanning it won't see it.

    Right now the only solution is to manually append it to the advertising data. Somehow I can't find the post I made about it now, but here's the code you need:

    NRF.setAdvertising({}, {name:"Blah"});
    // get normal advertising data
    var d = NRF.getAdvertisingData({});
    // append service UUID 0x1814
    var e = new Uint8Array(d.length+4);
    e.set([3,0x03, 0x14,0x18],d.length);
    // set new advertising data

    And don't bother specifying the service on the end of setServices.

  • Oh you absolute legend @Gordon - That worked!! I'd never have figured it out. Was going around in circles over several weekends 🤦

    Really looking forward to writing this project up over Christmas. Should have wrapped it up months ago.

  • Great! I'm wondering whether we should modify that bit of Espruino actually. It'd definitely make sense to pack everything into main advertising and use scan response only when it overflows.

  • @Gordon I think so, if it has caught other people out too. Anything that makes it more compatible with off the shelf products, that are impossible to debug on their end, is a good thing.

    Of course, even tho the Garmin can now see the Pixl, it can't "connect". But the same is true of the ESP32/Arduino. I bet it's related to the SC Control Point which at least I can test on the Pixl side.

  • Ok, just filed an issue https://github.com/espruino/Espruino/iss­ues/1966

    I bet it's related to the SC Control Point which at least I can test on the Pixl side.

    I'd still be a bit unsure about that. I reckon it'd be worth trying some of the other things first as I've never known anything to use the SC control point other than Nordic DFU

  • Thanks, yeah I'll focus on the simpler things first. But the SC Control Point on footpods (and similar bike equipment) enables the watch/phone to adjust the calibration of the device.

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

Adding Service Changed Characteristic to Generic Attribute Service?

Posted by Avatar for ConorONeill @ConorONeill