Custom ANT Broadcast using GenericChannel

Hello, 

Could anyone please provide some help on how to set up simple communication with ANT (not ANT+)?
I am very new to MonkeyC and would appreciate some help.

I currently have a simple data field deployed to the IQ store that right now only displays a simple runner ID. Right now, all I need is for the ANT payload to broadcast that 8-character long string over any ANT channel. 

Link to my app (currently does nothing):
https://apps.garmin.com/apps/6329a0c7-937b-490a-a8f7-f0f3ed859ed2

There is some information about the project on the data field description, but right now it doesn't do any communication. I want to just use the GenericChannel feature to broadcast the data.

I have posted a forum question before, but that was regarding BLE, not ANT.

What is the simplest way to send a simple 8-byte string over ANT with the fastest possible frequency? I know ANT+ is 4Hz but ANT is 200Hz, and I ideally would like more than 10Hz.
I have looked at the GenericChannel sample, but it is very long and honestly a little confusing, I feel like it could be simpler.

Any help is greatly appreciated!

  • Never did anything with broadcast. The closest I did is this: when an ANT HR monitor is connected (not to the watch in the system menu, but via GenericChannel) and I want to request the HRM to send page 9, I need to send a message:

    function requestPage9() as Void {
        var FF = 0xFF;
        var message = new Ant.Message();
        message.setPayload([0x46 /*70 - data page request*/, FF, FF, FF, FF, 0x04, 9, 1] as Array<Number>);
        GenericChannel.sendAcknowledge(message);
    }
    

    My guess is that if you use sendBroadcast it might do what you want.

  • Thank you! I'll try this out.

  • Apologies if this is a stupid question, but I currently have this as my code:

    import Toybox.Application;
    import Toybox.Lang;
    import Toybox.WatchUi;
    import Toybox.System;
    import Toybox.Ant;
    using Toybox.Ant;
    class PTCApp extends Application.AppBase
    {
    
        function initialize()
        {
            AppBase.initialize();
        }
    
        function onStart(state as Dictionary?) as Void
        {
        }
    
        function onStop(state as Dictionary?) as Void
        {
        }
    
        function onMessage(msg)
        {
        }
        function getInitialView() as[Views] or [ Views, InputDelegates ]
        {
            return [new PTCView()];
        }
    
        function getRunnerID() as String
        {
            var runnerID = Properties.getValue("runnerID");
            return runnerID;
        }
    }
    using Toybox.Ant;
    
    function sendRunnerID() as Void
    {
        var FF = 0xFF;
        var message = new Ant.Message();
        message.setPayload([ 0x46, FF, FF, FF, FF, 0x04, 9, 1 ] as Array<Number>);
        GenericChannel.sendBroadcast(message);
    }
    
    function getApp() as PTCApp
    {
        return Application.getApp() as PTCApp;
    }
    


    I haven't called the broadcastRunnerID yet, I am still struggling to get GenericChannel.sendBroadcast(message)
    to work at all. GenericChannel.sendAcknowledge also does not work. What am I missing?
    Line 44 keeps getting errors:

    fr745: Cannot find symbol ':sendBroadcast' on class definition '$.Toybox.Ant.GenericChannel'.

    I have tried with other watch models, still the same issue.

  • Sorry, my bad. So the code I sent is from a class that extends GenericChannel, so GenericChannel.x() just calls x() in the parent object. So you can either do it that way too or maybe try it in a different way: create a genericChannel object and call sendBroadcast on it

  • Okay, cool. There is nothing about ant channel configuration, however... how do I configure the RF and channel ID stuff?

  • could you provide an example on how to set up a GenericChannel object and broadcast it? That would be fantastic.

  • when an ANT HR monitor is connected (not to the watch in the system menu, but via GenericChannel)

    This came up before but there is no such thing as an ANT (not ANT+) HR monitor [*]. ANT+ is a standardized layer on top of ANT, and any non-BLE sensor that can show up in the built-in sensors menu of a Garmin device is ANT+.

    [*] ofc I mean HR monitors you can buy from the store and that will pair with Garmin devices using the built-in sensors menu

    Just because you use the Ant module instead of the AntPlus module in CIQ doesn't mean the sensor isn't ANT+.

    So it isn't quite right to suggest that your HR monitor isn't ANT+. You didn't say that explicitly but in the context of a question about "ANT (not ANT+)", that's how it comes across.

    It makes a difference because:

    - ANT (not ANT+) devices use a different network than ANT+ devices

    - ANT+ devices use a dedicated radio frequency in the ANT spectrum which should not be used by ANT devices

    - In Connect IQ, ANT channels can be set up as master (so they can broadcast unsolicited data). ANT+ channels cannot

    e.g. the following line produces a runtime error about not being allowed to set up the device as a master on the ant+ network:

    var channelAssign = new Ant.ChannelAssignment(Ant.CHANNEL_TYPE_TX_NOT_RX, Ant.NETWORK_ANTPLUS);

    (I think the idea is they don't want a CIQ app to masquerade as a fake HRM or something, based on a forum post from a CIQ team member from years ago)

    TL;DR

    - The Ant.GenericChannel class works with both ANT and ANT+ devices. (Here "ANT" means a device which is ANT and not ANT+)

    - OP is specifically asking about ANT and not ANT+ communication

    - Even though you have written code using Ant.GenericChannel to communicate with an ANT+ HRM, it's not 100% relevant to the OP's use case of sending a broadcast over ANT (not ANT+). Yes you would both use the GenericChannel class, but there are certain details which are different for OP's use case (not just because they want to send a broadcast, but because they are explicitly not using ANT+).

    It's tricky to talk about this because in one sense, any comm which is ANT+ is also ANT, but in another sense, some comm may be ANT (and not ANT+). Therefore, the meaning of "ANT" may be ambiguous, which is why many people use "ANT" and "ANT+" somewhat interchangeably. It's kind of like how HTTPS is built on top of HTTP, so when we say "HTTP", it's context-dependent on whether it means "HTTP (including HTTPS)" or "HTTP (not HTTPS)".

    www.thisisant.com/.../ant-antplus-defined

  • I haven't called the broadcastRunnerID yet, I am still struggling to get GenericChannel.sendBroadcast(message)
    to work at all. GenericChannel.sendAcknowledge also does not work. What am I missing?
    Line 44 keeps getting errors:

    fr745: Cannot find symbol ':sendBroadcast' on class definition '$.Toybox.Ant.GenericChannel'.

    In your code, you need an *instance* of the GenericChannel class to call the sendBroadcast method, but you are calling it on the class itself.

    The concepts of classes and instances of classes are foundational to object-oriented programming.

    https://stackoverflow.com/questions/1215881/the-difference-between-classes-objects-and-instances

    I'll try to explain it by way of an example.

    - House class: this is a blueprint with all the information necessary to build a house. It does not represent any specific house

    - instance of House (also known as an object): this is a specific house that exists in the world (created from the House class blueprint)

    In your case, Ant.GenericChannel has all the information to set up and use an ANT channel in Connect IQ, but it is not a channel in itself. As flocsy alluded to, when you type something like GenericChannel.X() in your code, it has different meanings depending on the context:

    1) outside of a GenericChannel-derived instance, it means to call a function X on the class itself (these are known as static class functions). This doesn't work for sendBroadcast because sendBroadcast requires an instance (it's a non-static function or an instance method/function)

    2) inside of a GenericChannel-derived instance, it means to call a function X on the instance, but to make sure to use the function defined on GenericChannel (the parent/ancestor of the current class).

    In Monkey C, you use the new keyword to create an instance of a class. The AntPlus.GenericChannel class has the following definition for its initialize() method, which is implicitly called when you use new()

    In you case, since you want to send ANT (not ANT+) broadcasts, you could create a channel as follows:

    class SomeClass {
        var channel as Ant.GenericChannel?;
    
        function onAntMessage(msg as Ant.Message) as Void {
            // do nothing?
        }
    
        function createGenericChannel() {
            var channelAssign = new Ant.ChannelAssignment(Ant.CHANNEL_TYPE_TX_NOT_RX, ANT.NETWORK_PUBLIC);
            channel = new GenericChannel(method(:onMessage), channelAssign);
        }
    }

    Ofc, that's just an example of creating an Ant.GenericChannel instance, it doesn't actually set up the channel, open it, or send a broadcast.

  • Here's a more complete example, incorporated into the code you posted. This example defines a BroadcastChannel class which extends Ant.GenericChannel (as flocsy alluded to and which is shown in the GenericChannel SDK sample). In simple terms, when class B extends class A:

    - an instance of B is an instance of A (but not vice versa)

    - B provides additional functionality above and beyond A

    In this case:

    - BroadcastChannel is an Ant.GenericChannel (but not vice versa)

    - BroadcastChannel has all of Ant.GenericChannel's functionality and more

    import Toybox.Application;
    import Toybox.Lang;
    import Toybox.WatchUi;
    import Toybox.System;
    import Toybox.Ant;
    
    class PTCApp extends Application.AppBase {
        var channel as BroadcastChannel?;
    
        function initialize() {
            AppBase.initialize();
        }
    
        public function onStart(state as Dictionary?) as Void {
            channel = new BroadcastChannel();
        }
    
        function getInitialView() as[Views] or [ Views, InputDelegates ] {
            return [new PTCView()];
        }
    
        function getRunnerID() as String {
            var runnerID = Properties.getValue("runnerID");
            return runnerID;
        }
    
        function sendRunnerID() as Void {
            var FF = 0xFF;
            var message = new Ant.Message();
            // I guess at some point you will use getRunnerID() instead of hardcoding it as follows
            message.setPayload([ 0x46, FF, FF, FF, FF, 0x04, 9, 1 ] as Array<Number>);
            channel.sendBroadcast(message);
        }
    }
    
    class BroadcastChannel extends Ant.GenericChannel {
        var channel as Ant.GenericChannel?;
    
        function onMessage(msg as Ant.Message) as Void { }
    
        function initialize() {
            var channelAssign = new Ant.ChannelAssignment(
                Ant.CHANNEL_TYPE_TX_NOT_RX, // Bidirectional Transmit (Master)
                Ant.NETWORK_PUBLIC // public ANT network (not ANT+)
            );
            GenericChannel.initialize(method(:onMessage), channelAssign);
    
            // https://www.thisisant.com/assets/resources/ANT%20Protocol/D00000652_ANT_Message_Protocol_and_Usage_Rev_5.1.pdf
            // https://developer.garmin.com/connect-iq/api-docs/Toybox/Ant/DeviceConfig.htm
            var deviceCfg = new Ant.DeviceConfig({
                :deviceNumber => 123, // default (device-specific) (cannot be 0 for master)
                :deviceType => 1,     // default (device-specific)
                :transmissionType => 0, // default "The manufacturer-specific transport type and extended device number"
    
                // :radioFrequency => 57, // (2457 MHz) this is the ANT+ frequency, and may not be used by ANT devices
    
                // For ANT any value from 0 - 124 (2400 MHz to 2524 MHz) is valid, except for 57 (2457 MHz is reserved for ANT+)
                //
                // For Connect IQ, only values 2 to 80 (2402 MHz to 2480 MHz) are valid
    
                // NOTE: change this to match the radio frequency that the receivers will be listening on
                :radioFrequency => 2, // EDIT: or maybe 10 would be better (DEFAULT_RADIO_FREQUENCY in https://developer.garmin.com/connect-iq/api-docs/Toybox/Ant/DeviceConfig.html)
    
                // messagePeriod is related to the message frequency in OP where you said that
                // ANT+ is 4 Hz and ANT is 200 Hz.
                // messagePeriod = 32768 / messageFrequency(Hz)
                // :messagePeriod => 8192, // default message period (4Hz)
                :messagePeriod => 164 // ~199.8 HZ
            });
            GenericChannel.setDeviceConfig(deviceCfg);
    
            GenericChannel.open();
        }
    }

    - note that the channel type is set to CHANNEL_TYPE_TX_NOT_RX - Bidirectional Transmit (Master). It's my understanding that it's necessary to set the channel as a master in order to transmit unsolicited broadcast data.

    I'm not sure if this will work in practice, but I ran the code in the sim and there were no runtime errors. I also called the sendRunnerID function in my test, without error

    Note that you can easily generate runtime errors in the sim by doing things such as:

    - trying to set ANT.CHANNEL_TYPE_TX_NOT_RX (master) for the ANT+ network

    - setting a radio frequency outside of the 2 to 80 range (2402 to 2480 MHz)

    So it at least proves that the sim does implement some runtime checks, and none of these runtime checks are triggered by the example code.

    References:

    [https://www.thisisant.com/developer/ant-plus/ant-antplus-defined]

    https://www.thisisant.com/assets/resources/ANT%20Protocol/D00000652_ANT_Message_Protocol_and_Usage_Rev_5.1.pdf

    https://developer.garmin.com/connect-iq/api-docs/Toybox/Ant/DeviceConfig.html

    https://developer.garmin.com/connect-iq/api-docs/Toybox/Ant/GenericChannel.html

  • Wow! Thank you for the help! I will try this out and let you know if it works or not.