Question how to distinguish components for electric SRAM shift systems via ANTPlus

I have written a routine for reading the battery status of components in electrical shift systems.

You get two components for each derailleur: one for the lever battery and one for the derailleur battery. The two batteries are easy to distinguish because the lever has about 2 volts and the derailleur has about 7 volts.

The problem starts with the rear and front switching systems. You get 4 components: 2 for levers and 2 for derailleurs.

How can you distinguish between front and rear batteries?

Okay, I also get the serial numbers for all 4 components, and you can look up which ones they are in the sensor information. However, that's not very convenient.

Has anyone solved this puzzle?

  • I don't really understand the problem. With the HR sensors I have 2 options: either connect to ANT ID 0, which connects to the closest sensor (which is obviously not going to work in case you have 2 sensors) or the user has to give the ANT ID. If you don't take note of the ANT ID-s, then how do you prevent from another bike's derailleur to connect to your app when you take over? So you'll need to have the ANT ID anyway IMHO.

    If I was trying to do this, and I'd assume the user will be alone (with no other derailleurs around) then I'd connect to the 2 devices, and then tell the user in the app: now click the front derailleur. Save the ANT ID, then ask the user to click the rear derailleur, save the ANT ID.

  • This is not about generic channel connection, but about reading connected ANT+ sensors.

    An electric shift system with front and rear derailleurs is connected to the Garmin device as a single sensor. The battery data can be read on the device under Sensors - Gear - Info and can be set as native datafield:

    This data can also be read from a CIQ data field via Toybox.AntPlus.Device.

    You’ll get 4 battery components in an array that cannot be clearly assigned to front and rear or left and right.
    That‘s my question.

       should know…  maybe you can answer?

  • This is how I do it. Seems to work. Except in many cases the SRAM shifting components are not reliable in terms of sending battery status on a regular basis. I sometimes get a good status from the shifters close to activity start and sometimes not at all. All the other types work well (radar, power meters, cadence sensor, speed sensor, lights, etc), but SRAM isn't as consistent. 
    TABLE 6-3, page 14, ANT PROFILE
    Identifier Value
    0 System
    1 Front Derailleur
    2 Rear Derailleur
    3 Left Shifter
    4 Right Shifter
    5 Shifter
    6 Left Extension Shifter
    7 Right Extension Shifter
    8 Extension Shifter 1
    9 Left Extension Shifter 2
    10 Right Extension Shifter 2
    11 Extension Shifter 2
    15 Unknown/Identified
    component = (payload[2] >> 4) & 0xF;
    BatStatus = (payload[7] >> 4) & 0x07;
    VoltLvl = (payload[7] & 0x0F) + (payload[6] / 256.0);

    switch (component) {
         case 0: // SYSTEM
         case 1: // FRONT SHIFTER
         etc


  • This is how I do it.

    Many thanks for your quick response!

    So, I would have to go for generic channel…

  • I use the ANT+ Listener.... and then grab the info during the onMessage interrupt processing. Sorry for the formatting... i seems to compress the white space.
    class MyShifting extends AntPlus.ShiftingListener {
        function initialize() { ShiftingListener.initialize(); }
    later....
       function onMessage(msg) {
            var payload, page, component, BatStatus, VoltLvl;

            payload = msg.getPayload();

            // Page ID is bits 0-6. eg: 01010010 = x52 = 82 = Batt Stat Page
            // so mask with 01111111 to get those bits
            page = payload[0] & 0x7F;

            // break out of this if data not valid
            if (page != 0x52) { return; }

            SHIFTING_SYS[DEVNUM] = msg.deviceNumber;
           
            component = (payload[2] >> 4) & 0xF;
            BatStatus = (payload[7] >> 4) & 0x07;
            VoltLvl = (payload[7] & 0x0F) + (payload[6] / 256.0);
  • This is a good post but I think you're missing something with the voltage level.

    As per the ANT+ shifting profile:

    - The "coarse voltage level" (byte 7, bits 0-3) ranges from 0-15, but 15 means invalid (battery voltage unknown)

    - The fractional voltage level (byte 6) ranges from 0-255, but 255 is an invalid value which must be ignored by the receiver (255 should be set when the coarse voltage level is 15.)

    An implementation for determining the voltage level that covers all the bases would look more like this:

    Reference: https://www.thisisant.com/resources/ant-device-profile-shifting/ (login required)

    Similarly:

    - Number of batteries is byte 2, bits 0-3. (Range: 0-15)

    - Battery Identifier is byte 2, bits 4-7. (Range: 0-15)

    If both the number of batteries field and the battery identifier field are 15, then it means both fields are unused.

  • Thanks I'll look into that, but it has been reporting what appears to be accurate fractional voltages. I'll check your feedback... ah ok you were just handling the unknown/invalid case - good marginal gain - I'll add that.

  • Thanks I'll look into that, but it has been reporting what appears to be accurate fractional voltages.

    I never said your code doesn't produce accurate fractional voltages in most cases.

    I said that there are 3 specific edge cases called out in the specification [*] which relate to the code you posted:

    - coarse voltage = 15, which means voltage unknown

    - fractional voltage = 255, which should be ignored

    - number of batteries and battery id (component in your code) both equal to 15, which means they should be ignored

    [*] specifically: ANT+ Device Profile - Shifting (Revision 2)

    It's entirely possible that your app has never encountered these in real life at all. Same goes for the num of batteries and battery id fields.

    It's also possible that you have been calculating and displaying a voltage of 15 + 255/256 in your app at one point or another, when in fact that's supposed to mean "unknown".

    It's pretty obvious from looking at the code I posted that if coarseVoltage is not 15 and fractionalVoltage is not 255, my code does the same thing as yours.

    It's simply a matter of reading the specification and writing code that adheres to it. In this case it's not even very hard to so (assuming you can access the specification **), it just requires you to care about writing code that works as well as possible.

    (** Hint: even if you don't have an account on the ANT site, there is a way to find working credentials.)

    https://www.thisisant.com/resources/ant-device-profile-shifting/ 

    Quality software development isn't about writing code that works only in the best case, or even 95%-99% of the time, although I can see some why people might think so, given how the state of the tech industry.

    Personally I don't care how well your app runs for your own purposes, but since you are giving advice to other people, I want to clarify that the code that was provided could use some improvement.

  • Oh - this might also help - my SWITCH statement to handle all the cases of various shifting setups, like my MTB with a 1x12 setup with no FD or front FD shift lever.

    Case 0: overall system battery level

    Case 1: front derailleur

    Case 2: rear derailleur

    Case 3,6,9: left shifter

    Case 4,5,7,8,10,11: right shifter