Skip to content

Commit

Permalink
Generic ANT Channel Data Field and Barrel
Browse files Browse the repository at this point in the history
  • Loading branch information
Ben Pryhoda committed Oct 14, 2020
1 parent 8f7f6d0 commit a72b6fc
Show file tree
Hide file tree
Showing 29 changed files with 1,065 additions and 1 deletion.
17 changes: 17 additions & 0 deletions barrels/GenericChannelHeartRateBarrel/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>GenericChannelHeartRateBarrel</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>connectiq.barrelBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>connectiq.barrelProjectNature</nature>
</natures>
</projectDescription>
2 changes: 2 additions & 0 deletions barrels/GenericChannelHeartRateBarrel/.settings/IQ_IDE.prefs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
eclipse.preferences.version=1
project_manifest=manifest.xml
2 changes: 2 additions & 0 deletions barrels/GenericChannelHeartRateBarrel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# GenericChannelHeartRateBarrel
This barrel demonstrates how to use the Connect IQ Generic ANT Channel module to connect to an ANT+ Heart Rate monitor. This project can be adapted to work with any ANT or ANT+ device. See the [Generic ANT+ Heart Rate Data Field](https://github.com/garmin/connectiq-apps/tree/master/datafields/GenericAntPlusHeartRateField) for an example project that uses this barrel.
11 changes: 11 additions & 0 deletions barrels/GenericChannelHeartRateBarrel/manifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- This is a generated file. It is highly recommended that you DO NOT edit this file. --><iq:manifest xmlns:iq="http://www.garmin.com/xml/connectiq" version="3">
<iq:barrel id="445b1620-8d6b-4305-b40e-b7f9b56363ec" module="GenericChannelHeartRateBarrel" version="0.0.1">
<iq:products/>
<iq:permissions>
<iq:uses-permission id="Ant"/>
</iq:permissions>
<iq:languages/>
<iq:barrels/>
<iq:annotations/>
</iq:barrel>
</iq:manifest>
2 changes: 2 additions & 0 deletions barrels/GenericChannelHeartRateBarrel/monkey.jungle
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
project.manifest = manifest.xml

208 changes: 208 additions & 0 deletions barrels/GenericChannelHeartRateBarrel/source/AntPlusHeartRateSensor.mc
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
using Toybox.Ant;

module GenericChannelHeartRateBarrel {

class AntPlusHeartRateSensor extends Toybox.Ant.GenericChannel {
// Channel configuration
private const CHANNEL_PERIOD = 8070; // ANT+ HR Channel Period
private const DEVICE_TYPE = 120; // ANT+ HR Device Type
private const RADIO_FREQUENCY = 57; // ANT+ Radio Frequency
private const SEARCH_TIMEOUT = 1; // 2.5 second search timeout
private const DISABLED = 0;

// Message indexes
private const MESSAGE_ID_INDEX = 0;
private const MESSAGE_CODE_INDEX = 1;

// Proximity bin defines
private const WILDCARD_PAIRING = 0;
private const CLOSEST_SEARCH_BIN = 1;
private const FARTHEST_SEARCH_BIN = 10;
private const PROXIMITY_DISABLED = 0;

// Variables
hidden var chanAssign;
hidden var deviceCfg;
hidden var deviceNumber;
hidden var transmissionType;
hidden var searchThreshold;
hidden var hrSensorDelegate;
hidden var onUpdateCallback;
hidden var onPairedCallback;
hidden var isClosed; // Tracks when the app wants us to stay closed
hidden var isPaired; // Paired is an event we only fire once

var data;

// Initializes AntPlusHeartRateSensor, configures and opens channel
// @param extendedDeviceNumber, a 20-bit ANT+ defined integer used for identification
// @param isProximityPairing, true enables pairing based on signal strength from strongest to weakest
function initialize( extendedDeviceNumber, isProximityPairing ) {

if (extendedDeviceNumber == WILDCARD_PAIRING) {
deviceNumber = WILDCARD_PAIRING;
transmissionType = WILDCARD_PAIRING;
} else {
parseExtendedDeviceNumber( extendedDeviceNumber );
}

if ( isProximityPairing ) {
searchThreshold = CLOSEST_SEARCH_BIN;
} else {
searchThreshold = WILDCARD_PAIRING;
}

data = new LegacyHeartData();

// Create channel assignment
chanAssign = new Toybox.Ant.ChannelAssignment(
Toybox.Ant.CHANNEL_TYPE_RX_NOT_TX,
Toybox.Ant.NETWORK_PLUS);

// Initialize the channel through the superclass
GenericChannel.initialize( method(:onMessage), chanAssign );

// Set the configuration
deviceCfg = new Toybox.Ant.DeviceConfig( {
:deviceNumber => deviceNumber,
:deviceType => DEVICE_TYPE,
:transmissionType => transmissionType,
:messagePeriod => CHANNEL_PERIOD,
:radioFrequency => RADIO_FREQUENCY,
:searchTimeoutLowPriority => SEARCH_TIMEOUT,
:searchTimeoutHighPriority => DISABLED,
:searchThreshold => searchThreshold} );
GenericChannel.setDeviceConfig( deviceCfg );

// The channel was initialized into a CLOSED state
isClosed = true;

// The channel has not paired with a device yet
isPaired = false;

hrSensorDelegate = null;
onUpdateCallback = null;
}

// Opens the generic channel
function open() {
isClosed = false; // Externally opening the channel means it is no longer CLOSED
deviceCfg.searchThreshold = searchThreshold;
GenericChannel.setDeviceConfig( deviceCfg );
GenericChannel.open();
}

// Closes the generic channel
function close() {
isClosed = true; // Externally closing the channel means it will stay CLOSED
GenericChannel.close();
}

// Release the generic channel
// Once the channel is released it cannot be re-opened or closed again
function release() {
GenericChannel.release();
}

// Sets the delegate handler for asynchronous sensor events
// An application can only have 1 registered delegate. Subsequent calls to this function will override the current delegate.
// Setting this to null will remove any registered delegate.
function setDelegate( hrSensorDelegate ) {
hrSensorDelegate = hrSensorDelegate;

if ( hrSensorDelegate != null ) {
onUpdateCallback = hrSensorDelegate.method(:onHeartRateSensorUpdate);
onPairedCallback = hrSensorDelegate.method(:onHeartRateSensorPaired);
} else {
onUpdateCallback = null;
onPairedCallback = null;
}
}

// Returns the current extended device number.
// This will change to the sensor's value once paired.
function getExtendedDeviceNumber () {
return (deviceNumber | ((transmissionType & 0xF0) << 12));
}

// On new ANT Message, parses the message
// @param msg, a Toybox.Ant.Message object
function onMessage( msg ) {
// Parse the payload
var payload = msg.getPayload();

if ( Toybox.Ant.MSG_ID_CHANNEL_RESPONSE_EVENT == msg.messageId ) {
if ( Toybox.Ant.MSG_ID_RF_EVENT == payload[MESSAGE_ID_INDEX] ) {
// React to changes in the ANT channel state
switch(payload[MESSAGE_CODE_INDEX]) {

// Drop to search occurs after 2s elapse or 8 RX_FAIL events, whichever comes first
case Toybox.Ant.MSG_CODE_EVENT_RX_FAIL_GO_TO_SEARCH:
// Reset HR data after missing over 2s of messages
data.reset();
if ( onUpdateCallback != null ) {
onUpdateCallback.invoke(data.computedHeartRate);
}
break;

// Search timeout occurs after SEARCH_TIMEOUT duration passes without pairing
case Toybox.Ant.MSG_CODE_EVENT_RX_SEARCH_TIMEOUT:
// Only change the search threshold if proximity pairing is enabled
if ( searchThreshold != PROXIMITY_DISABLED ) {
// Expand search radius after each channel close event due to search timeout
if ( searchThreshold < FARTHEST_SEARCH_BIN ) {
searchThreshold++;
} else {
// Pair to any signal strength if we've searched every bin
searchThreshold = WILDCARD_PAIRING;
}

}
break;

// Close event occurs after a search timeout or if it was requested
case Toybox.Ant.MSG_CODE_EVENT_CHANNEL_CLOSED:
// Reset HR data after the channel closes
data.reset();

if ( onUpdateCallback != null ) {
onUpdateCallback.invoke(data.computedHeartRate);
}

// If ANT closed the channel, re-open it to continue pairing
if(!isClosed) {
open();
}
break;
}
}

} else if ( Toybox.Ant.MSG_ID_BROADCAST_DATA == msg.messageId ) {
data.parse( payload ); // Parse payload into data

if ( onUpdateCallback != null ) {
onUpdateCallback.invoke(data.computedHeartRate); // Pass data to callback
}

if ( !isPaired ) {
isPaired = true; // Only fire paired event once

deviceNumber = msg.deviceNumber;
transmissionType = msg.transmissionType;

if ( onPairedCallback != null ) {
onPairedCallback.invoke(getExtendedDeviceNumber());
}
}
}
}

// Parses the 20-bit extended device number into its two separate components
// @param extendedDeviceNumber, a 20-bit ANT+ defined integer used for identification
private function parseExtendedDeviceNumber( extendedDeviceNumber ) {
// Parse the extended device number for the upper nibble
transmissionType = ((extendedDeviceNumber >> 12) & 0xF0) | 0x01;
deviceNumber = extendedDeviceNumber & 0xFFFF;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module GenericChannelHeartRateBarrel {

// Delegate Class for ANT+ Heart Rate Sensor Callbacks.
class HeartRateSensorDelegate {

// If the sensor is being tracked this will be called with the latest data.
function onHeartRateSensorUpdate( computedHeartRate ) {
}

// If the extended device number was wildcarded at initialization this will be called with the paired value.
function onHeartRateSensorPaired( extendedDeviceNumber ) {
}
}
}
25 changes: 25 additions & 0 deletions barrels/GenericChannelHeartRateBarrel/source/LegacyHeartData.mc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module GenericChannelHeartRateBarrel {

// Represents the data available from any ANT+ Heart Rate strap
class LegacyHeartData {
private static const COMPUTED_HR_INDEX = 7;
private static const INVALID_HR = 0;

var computedHeartRate;

function initialize() {
computedHeartRate = INVALID_HR;
}

// Parses the computed heart rate value from the sensor
// @param payload, application data from an ANT broadcast message
function parse( payload ) {
computedHeartRate = payload[COMPUTED_HR_INDEX];
}

// Sets the computed heart rate value to INVALID
function reset() {
computedHeartRate = INVALID_HR;
}
}
}
3 changes: 3 additions & 0 deletions barrels/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@ The Semicircles barrel provides an abstract coordinate type that speeds up posit

### **[BluetoothMeshBarrel](https://github.com/connectiq-apps/tree/master/barrels/BluetoothMeshBarrel)**
This Bluetooth Mesh barrel provides an abstract library for connecting your Connect IQ app to a Bluetooth Mesh network. See the [blog post](https://forums.garmin.com/developer/connect-iq/b/news-announcements/posts/bluetooth-mesh-networking-with-connect-iq) for details.

### **[GenericChannelHeartRateBarrel](https://github.com/connectiq-apps/tree/master/barrels/GenericChannelHeartRateBarrel)**
This barrel demonstrates how to use the Connect IQ Generic ANT Channel module to connect to an ANT+ Heart Rate monitor. This project can be adapted to work with any ANT or ANT+ device. See the [Generic ANT+ Heart Rate Data Field](https://github.com/garmin/connectiq-apps/tree/master/datafields/GenericAntPlusHeartRateField) for an example project that uses this barrel.
17 changes: 17 additions & 0 deletions datafields/GenericAntPlusHeartRateField/.project
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>GenericAntPlusHeartRateField</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>connectiq.builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>connectiq.projectNature</nature>
</natures>
</projectDescription>
2 changes: 2 additions & 0 deletions datafields/GenericAntPlusHeartRateField/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# GenericAntPlusHeartRateField
A Connect IQ Simple Data Field that uses the [Generic Channel Heart Rate Barrel](https://github.com/garmin/connectiq-apps/tree/master/barrels/GenericChannelHeartRateBarrel) to connect to an ANT+ Heart Rate Monitor. This data field demonstrates using barrels, app settings, on-device app settings, FIT Developer Fields, and unit tests.
6 changes: 6 additions & 0 deletions datafields/GenericAntPlusHeartRateField/barrels.jungle
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Do not hand edit this file. To make changes right click
# on the project and select "Configure Monkey Barrels".

GenericChannelHeartRateBarrel = [../../barrels/GenericChannelHeartRateBarrel/monkey.jungle]
base.barrelPath = $(base.barrelPath);$(GenericChannelHeartRateBarrel)

20 changes: 20 additions & 0 deletions datafields/GenericAntPlusHeartRateField/manifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<!-- This is a generated file. It is highly recommended that you DO NOT edit this file. --><iq:manifest xmlns:iq="http://www.garmin.com/xml/connectiq" version="3">
<iq:application entry="GenericAntPlusHeartRateFieldApp" id="a658988723e947919e496ced36a6fbc8" launcherIcon="@Drawables.LauncherIcon" minSdkVersion="1.4.0" name="@Strings.AppName" type="datafield" version="0.0.2">
<iq:products>
<iq:product id="edge1030plus"/>
<iq:product id="fenix6"/>
<iq:product id="fenix6pro"/>
<iq:product id="fr945"/>
</iq:products>
<iq:permissions>
<iq:uses-permission id="Ant"/>
<iq:uses-permission id="FitContributor"/>
</iq:permissions>
<iq:languages>
<iq:language>eng</iq:language>
</iq:languages>
<iq:barrels>
<iq:depends name="GenericChannelHeartRateBarrel" version="0.0.1"/>
</iq:barrels>
</iq:application>
</iq:manifest>
1 change: 1 addition & 0 deletions datafields/GenericAntPlusHeartRateField/monkey.jungle
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
project.manifest = manifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<drawables>
<bitmap id="LauncherIcon" filename="launcher_icon.png" />
</drawables>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit a72b6fc

Please sign in to comment.