ANT+ (ebike) Datafield for EDGE - Problem to reinitialize ANT channel after wakeup from sleep

I built a small datafield to learn about ANT+ for ebikes (LEV - Light Electric Vehicel) on the base of the sample "Moxy Datafield".

My test datafield works pretty well - but there is a (small) problem:
When the Edge was sent to sleep - after wakeup the ANT channel does not exist anymore and needs to be re-initialized.

GARMIN says (years ago...):
...the issue is that when the Edge enters low power, we power down the ANT module and forget the state of all running channels (we also reset the state of the ANT radio stack). Then when the Edge exits low power mode the data field expects its channel to still be open. Calling open/close after exiting low power wouldn't work in this case as the original channel would have been reset.
Until we get a fix in for this an app developer will need to set up a watchdog for their channel where if they don't receive a message for X seconds (with X being larger than their search timeout), the developer will reinitialize their channel.

So, I built a watchdog in view.mc which works.

        /// WATCHDOG:
        //lastMessage = sensor.getData().getLastMessage();  // see line above!
        timeNow = Time.now().value();
        if (lastMessage != null) {
            if ( lastMessage < timeNow - 5 ) {
                // WatchDog barks
                levStatus = 0;
                lastMessage = null;
                // now reInitialize the ANT channel - BUT HOW TO DO?????
            } 
        }

But I don't find a way to call a re-initialize of the ANT channel in LEVSensor.mc.

I submit the whole (small) datafiedl, so - if you want - can better see.

LEVField.zip

And a screenshot of the Edge for better information:

I would be glad if any of you can show me the solution.

Many thanks in advance!

Top Replies

All Replies

  • TL;DR there must be a member or global variable that stores an instance of your sensor class (which extends Ant.GenericChannel) somewhere. To "reinitialize" the channel, you need to destroy that instance (by setting all references to null) and create a new instance.

    ---

    I had a similar problem with a running power field that was designed to talk to the Stryd power meter. Users would report that the sensor would sometimes disconnect and never recover. Stryd shared their solution with me which is basically what you said: reinitialize the channel.

    The (messy) way I did it was to store the only reference to my sensor class (which extends Ant.GenericChannel) in a member variable of the datafield's view. When the sensor class detects that the channel was closed, it sets a member variable to ask the view to reinitialize the channel. The view reinitializes the channel by setting its sensor member variable to null, then creating a new instance of the sensor class.

    Here's a snippet of what I have:

    class PowerSensor extends Ant.GenericChannel {
        // ...
        var deviceNumber = null; // initialized from config
        var reopen = false;
        function onMessage(msg) {
            // Parse the payload
            var payload = msg.getPayload();
    		var payload0 = payload[0];
    		var payload1 = payload[1];
    		
    		if (msg.messageId == Ant.MSG_ID_BROADCAST_DATA) {
    	    	// ...
            } else if (msg.messageId == Ant.MSG_ID_CHANNEL_RESPONSE_EVENT) {
                if (payload0 == Ant.MSG_ID_RF_EVENT) {
                    if (payload1 == Ant.MSG_CODE_EVENT_CHANNEL_CLOSED) {
                        // Channel closed, re-open
                        reopen = true;
                    } else if (payload1 == Ant.MSG_CODE_EVENT_RX_FAIL_GO_TO_SEARCH) {
                        // ...
                    }
                }
            }
        }
    }
    
    class DataFieldView extends WatchUi.DataField {
        //...
        function compute() {
            // ...
      	    if (powerSensor != null) {
        		if (powerSensor.reopen)	{
        			var devNum = powerSensor.deviceNumber;
        			mSensor = null;
        			powerSensor = null;
        			powerSensor = new PowerSensor(devNum);
        			mSensor = powerSensor;
        		}
    		}
        }
    }

    Ofc my solution doesn't use a watchdog, but it seems that could also work.

    And of course, the MoxyField sample stores the sensor in two places (app class and view class), so you'd have to take that into account when implementing your solution. It might be more efficient/simple to just store the sensor in the view class.

  • For your specific codebase, these are the changes I would make (I haven't tested them, but the code compiles).

    diff --color -crB LEVField/source/LEVFieldApp.mc LEVField-mod/source/LEVFieldApp.mc
    *** LEVField/source/LEVFieldApp.mc	Thu Nov 23 15:34:02 2023
    --- LEVField-mod/source/LEVFieldApp.mc	Thu Nov 23 21:52:01 2023
    ***************
    *** 9,14 ****
    --- 9,15 ----
      
      class LEVFieldApp extends Application.AppBase {
          private var _sensor as LEVSensor?;
    +     private var _view as LEVFieldView?;
      
          //! Constructor
          public function initialize() {
    ***************
    *** 39,45 ****
          //! Return the initial view for the app
          //! @return Array [View]
          public function getInitialView() as Array<Views or InputDelegates>? {
              //return [new $.LEVFieldView(_sensor)] as Array<Views>;     // $.  ...?
    !         return [new LEVFieldView(_sensor)] as Array<Views>;     // funktioniert auch ohne $.   aber dauert länger?  
          }
      }
    --- 40,56 ----
          //! Return the initial view for the app
          //! @return Array [View]
          public function getInitialView() as Array<Views or InputDelegates>? {
    +         _view = new LEVFieldView(_sensor);
              //return [new $.LEVFieldView(_sensor)] as Array<Views>;     // $.  ...?
    !         return [_view] as Array<Views>;     // funktioniert auch ohne $.   aber dauert länger?  
    !     }
    ! 
    !     public function reinitializeSensor() {
    !         _sensor = null;
    !         _sensor = new LEVSensor();
    !         _sensor.open();
    !         if (_view != null) {
    !             _view.setSensor(_sensor);
    !         }
          }
      }
    diff --color -crB LEVField/source/LEVFieldView.mc LEVField-mod/source/LEVFieldView.mc
    *** LEVField/source/LEVFieldView.mc	Thu Nov 23 21:50:43 2023
    --- LEVField-mod/source/LEVFieldView.mc	Thu Nov 23 21:52:41 2023
    ***************
    *** 41,46 ****
    --- 41,50 ----
              _sensor = sensor;
          }
      
    +     public function setSensor(sensor as LEVSensor) {
    +         _sensor = sensor;
    +     }
    + 
          //##################################################################
          function compute(info as Activity.Info) as Void {
      
    ***************
    *** 91,96 ****
    --- 95,101 ----
                      levStatus = 0;
                      lastMessage = null;
                      // now reInitialize the ANT channel - BUT HOW TO DO?????
    +                 (Application.getApp() as LEVFieldApp).reinitializeSensor();
                  } 
              }
      
    

  • Thank you  for your efforts!

    I will report back!

  • HEUREKA!

    , I thank you very much for that genius solution! Works perfectly!

  • Sure np. Tbh, I have to give all the credit to the Stryd team for giving me the solution.

  • Hi, again, !

    I found a small negative side effect in coding my "watchdog" - but, again, I find no code solution:

    We will forget the sleep thing for now. Once the channel was establishen and all is working fine, it could be necessary that one turnes off the ebike and turns it on again (without Edge going to sleep).
    In that case the ANT channel remains and without "watchdog" "onMessage" will beginn to work after ebike turn on.

    In my code case the watchdog cannot distinguish if "noMessage" is bacause of sleep or short turn off the bike - and it will bark although the channel was not closed. And then - no connection is possible anymore.

    So, I think the watch dog should not watch only the "timeout" but also wether a ANT channel exists or not...
    (to turn off of the ebike starts the timeout the same as sleep mode...)

    May I again ask you for help?

    Thanks so much!

  • Instead of using a watchdog timer, can you look for Ant.MSG_CODE_EVENT_CHANNEL_CLOSED instead, as in the first example?

    This is what I do in my Stryd data field, and that's what Stryd did in their own data field IIRC. Before I implemented that check, I had complaints about the data field permanently disconnecting from the Stryd sensor, but after I added the check, there were no more complaints.

  • Instead of using a watchdog timer, can you look for Ant.MSG_CODE_EVENT_CHANNEL_CLOSED instead


    Thanks again for your comment - which I have studied. But there's one thing I don't get:

    When the Edge is woken up from sleep there will no "onMessage()" event happen. So, I can't askt for Ant.MSG_CODE_EVENT_CHANNEL_CLOSED. 

    Somehow the cat bites its own tail...

    Is there really no tool in Monkey C that determines whether an Edge device has been woken up from sleep mode...?

  • If it helps, I "solved" this issue by storing the "lastMessageTime" and check the value in the "onUpdate" method. You can check the source code for more details: https://github.com/maca88/E-Bike-Edge-MultiField/blob/master/Source/EBikeEdgeMultiField/source/EBikeMultiField.mc#L72

  • Is there really no tool in Monkey C that determines whether an Edge device has been woken up from sleep mode...?

    I don't own an Edge so I don't know much about sleep mode except for what the manuals say (that it turns off all ANT+ sensors, GPS and Bluetooth).

    Can you cover all the bases by doing the following on a timeout:

    - Call close() on the sensor class

    - Destroy sensor class, reinitialize it

    Yes, you will be unnecessarily closing and reopening the channel in some cases, but hopefully it will work.

    I can't believe this isn't an issue that every Edge developer would encounter (meaning I'm surprised Garmin doesn't seem to have a nice way of handling it.) But that's CIQ for ya.