Smart Bike Lights (Data Field) / Bike Lights Control (Widget)

I've made two open source CIQ applications to control ANT+ lights:

- Smart Bike Lights (data field): https://apps.garmin.com/en-US/apps/0d9fd828-c932-4470-9c37-fd2828881888 

      Configurator: https://maca88.github.io/SmartBikeLights/ 

- Bike Lights Control (widget): https://apps.garmin.com/en-US/apps/fa3c2332-76e3-4ba1-8528-32a0fd617ab1 

     Configurator: https://maca88.github.io/BikeLightsControl/ 

Smart Bike Lights features:
- Automatic light control (Smart mode) based on the configured filters (sunset, sunlight, speed, ...)
- Manual light control (only for Edge devices with a touch screen or devices with CIQ 3.2 and more than 32KB memory)
- Records lights modes that are displayed in Garmin Connect

Bike Lights Control features:
- Manual light control (available to all devices)

Both applications can be installed on the same device, which can be useful for low end devices (e.g. Edge 520) where "Bike Lights Control" can be used to change light modes manually and "Smart Bike Lights" for automatic light control.

Currently registered ANT+ lights:
- Bontrager Ion Pro RT
- Bontrager Ion 200 RT
- Bontrager Flare RT
- Garmin Varia RTL500
- Garmin Varia RTL510
- Garmin Varia RTL515
- Garmin Varia HL500
- Garmin Varia UT800

in case your ANT+ light is not on the list you can help by doing the following:

1. Install Bike Lights Control
2. Pair your bike light with your Garmin device
3. Open the widget:
- Edge with a touch screen: post which numbers are displayed on the buttons and what mode each button represents when pressed
- Other devices: Select the "Light modes" menu and post which numbers are displayed in the menu and what mode each menu item represents when selected

Example for Bontrager Ion Pro RT:


1 -> High steady beam
2 -> Medium steady beam
5 -> Low steady beam
63 -> Day flash
62 -> Night flash

There are some features that could be possible to add in the future, but would require Garmin to add them to the CIQ api. In case you are interested in any of the below features, consider upvoting their feature request thread:

1. Control lights based on the ambient light sensor available on some devices:
https://forums.garmin.com/developer/connect-iq/i/bug-reports/ambient-light-sensor-api-access 

Known issues:

1. Unable to use Tempe Visualizer or TyreWiz Data Field alongside Smart Bike Lights:
https://forums.garmin.com/developer/connect-iq/i/bug-reports/lightnetworklistener-onbikelightupdate-not-called-when-certain-data-fields-are-installed

  • Thanks so much - works perfect now. Funny thing is that it worked ok on my 520 Plus but clearly I entered something wrong for my new 840. I appreciate your help!

  • BirdLight.txt
    using Toybox.Graphics as Gfx;
    using Toybox.WatchUi as Ui;
    using Toybox.Application as App;
    using Toybox.AntPlus;
    
    var xListenr;
    var xNetwork;
    var xNmode;
    var xLight;
    var xLmode;
    
    var xTest=0;
    
    
    class MyLightNetworkListener extends AntPlus.LightNetworkListener
    {
    	var lList;
    	var lLight;
    
    	
        function onLightNetworkStateUpdate(data)
    	{
    		xNmode=data;	// LIGHT_NETWORK_STATE: NOT_FORMED = 0, FORMING = 1, FORMED = 2
    		xTest+=1000000;
    		
            if (data==AntPlus.LIGHT_NETWORK_STATE_FORMED)
    		{
    			lList= xNetwork.getBikeLights();
    			if (lList != null)
    			{
    				// nMode=xNetwork.getNetworkMode();    
    				
    				var i=0, found=0;
    				for (i=0; i<lList.size(); i++)
    				{
    					lLight=lList[i];
    					if (lLight.type==AntPlus.LIGHT_TYPE_TAILLIGHT)
    					// LIGHT_TYPE_HEADLIGHT 0, LIGHT_TYPE_TAILLIGHT 2, LIGHT_TYPE_SIGNAL_CONFIG 3, LIGHT_TYPE_SIGNAL_LEFT 4, LIGHT_TYPE_SIGNAL_RIGHT 5, LIGHT_TYPE_OTHER 7
    					{
    						xLight=lLight;
    						xLmode=lLight.mode;
    						found=1;
    						//xLid=lLight.identifier;
    						//xLman=xNetwork.getManufacturerInfo(xLid).modelNumber; // .manufacturerId;
    						//xLprd=xNetwork.getProductInfo(xLid).serial;
    						//xLbat=xNetwork.getBatteryStatus(xLid).batteryStatus;	// batteryVoltage=-1.000;
    					}
    					if (found==0)
    					{
    						xLight=null;
    					}
    				}   
    			}
    			else
    			{
    				xLight=null;
    			}
    		}
        }
    }
    
    class BirdField extends Ui.DataField
    {
    
    	var aRadar= new[8];
    	var bRadar;
    
    	function initialize()
    	{
            xListenr= new MyLightNetworkListener();
            xNetwork= new AntPlus.LightNetwork(xListenr);
    		bRadar=	  Toybox.AntPlus has :BikeRadar ? new AntPlus.BikeRadar(null) : null;
    		View.initialize();
    	}
    
    	
    	function onLayout(dc) as Void
    	{
    		dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT);
    		dc.fillRectangle(0, 0, dc.getWidth(), dc.getHeight());
    	}
    
    
    	function onShow() as Void
    	{
    	}
    
    
    	function onUpdate(dc) as Void
    	{
    		var i;
    		var bat;
    		
    		xTest+=1;
    		dc.setColor(Graphics.COLOR_BLACK, Graphics.COLOR_TRANSPARENT);
            dc.drawText(10,350,Gfx.FONT_TINY,xTest,Gfx.TEXT_JUSTIFY_LEFT);
    		
    		for(i=0; i<8; i++)
    		{
    			dc.drawText(10,i*30,Graphics.FONT_SMALL,aRadar[i],Graphics.TEXT_JUSTIFY_LEFT);
    		}
    	
    		if (xNmode==2)
    		{
                dc.drawText(10,400,Gfx.FONT_TINY,"NETWORK READY",Gfx.TEXT_JUSTIFY_LEFT);
                dc.drawText(10,430,Gfx.FONT_TINY,"LIGHT: "+xLight.identifier,Gfx.TEXT_JUSTIFY_LEFT);
    			if (xLight!=null)
    			{
    				dc.drawText(10,460,Gfx.FONT_TINY,"MODE: "+xLmode+", "+xLight.mode,Gfx.TEXT_JUSTIFY_LEFT);
    				bat=xNetwork.getBatteryStatus(xLight.identifier).batteryStatus;	// batteryVoltage=-1.000;
    				// BATT_STATUS_NEW = 1, BATT_STATUS_GOOD = 2, BATT_STATUS_OK = 3, BATT_STATUS_LOW = 4, BATT_STATUS_CRITICAL = 5 
    				dc.drawText(10,490,Gfx.FONT_TINY,"BATT: "+bat,Gfx.TEXT_JUSTIFY_LEFT);
    			}
    		
    		}
    		else if (xNmode==1)
    		{
                dc.drawText(10,400,Gfx.FONT_TINY,"NETWORK INIT",Gfx.TEXT_JUSTIFY_LEFT);
    		}
    		else
    		{
                dc.drawText(10,400,Gfx.FONT_TINY,"NETWORK DOWN",Gfx.TEXT_JUSTIFY_LEFT);
    		}
    		
    
    	/*
    		var mode=(mode+1)%20;
            if (mode==10)
    		{
    			dc.drawText(10,730,Graphics.FONT_SMALL,"RESET",Graphics.TEXT_JUSTIFY_LEFT);		
    			// xNetwork.restoreTaillightsNetworkModeControl();
            }
            else if (mode==0)
    		{
    			dc.drawText(10,730,Graphics.FONT_SMALL,"PULSE",Graphics.TEXT_JUSTIFY_LEFT);		
    			xNetwork.setTaillightsMode(6);
    			// xLight.setMode(6);
    			// xNetwork.setTaillightsMode(AntPlus.LIGHT_MODE_FAST_FLASH); // 0: off, 4:ein, 5:hell, 6:pulse, 7:blink
    			// xNetwork.restoreTaillightsNetworkModeControl();		
    		}
    	*/
    	
    	}
    
    	function compute(info)
    	{
    		xTest+=1000;
         
    		var radarInfo = bRadar.getRadarInfo();
    		if(radarInfo)
    		{
    			for(var i = 0; i < 8; i++)
    			{
    				aRadar[i] = radarInfo[i].range;
    				// .range (m)		float
    				// .speed (m/s)		float
    				// .threat (level)	0=THREAT_LEVEL_NO_THREAT, 1=THREAT_LEVEL_VEHICLE_APPROACHING, 2=THREAT_LEVEL_VEHICLE_FAST_APPROACHING
    				// .threatside		0=THREAT_SIDE_NO_SIDE, 1=THREAT_SIDE_RIGHT, 2=THREAT_SIDE_LEFT
    			}
    		}
    		
    	}
    
    	function onHide() as Void 
    	{
    	}
    }
    
    class BirdApp extends App.AppBase
    {
        function getInitialView()	{	return [new BirdField()];	}
        function initialize()		{	AppBase.initialize();		}
        function onStart(state)		{								}
        function onStop(state)		{	/*return false;*/			}
    }
    

    Cool... but heavy stuff for beginners Wink

    I'm trying to make a micro version of your wonderful app - which is complicate because of a non working Ant+ dongle, so I need to transfer every new version to my real edge device instead of using the simulator Disappointed

    However, it looks like I can check the actual light mode (which is not shown imediately but when swapping data field pages for a while) and I am also able to change it to another mode. But how to check easily the 'network mode' shown as "N" or "M" in your app? And how to force to switch the device to the mode "N"?.

    Here's my all-in-one source code showing my first steps (sorry, can't add it as source code, tried to attach it as file)...

     

  • which is complicate because of a non working Ant+ dongle, so I need to transfer every new version to my real edge device instead of using the simulator Disappointed

    I had the same issue, you need to buy a newer dongle or switch to an older SDK (4.0.10 or lower). You can check for more info here:
    https://forums.garmin.com/developer/connect-iq/i/bug-reports/regression-bug-ant-genericchannel-does-not-work-in-simulator-with-sdk-4-1-x-anymore

    But how to check easily the 'network mode' shown as "N" or "M" in your app? And how to force to switch the device to the mode "N"?.

    As far as I know, there is no easy check for this. When you change the Garmin network mode by using the native Garmin menu, the LightNetwork instance that you have in your application will not be updated until it is recreated. There is a comment in Smart Bike Lights code for this:

    https://github.com/maca88/SmartBikeLights/blob/master/Source/SmartBikeLights/source-preprocess/BikeLightsView.mc#L1433

    and here:

    https://github.com/maca88/SmartBikeLights/blob/master/Source/SmartBikeLights/source-preprocess/BikeLightsView.mc#L474

    Smart Bike Lights is basically recreating the light network every time onShow is called if the last update time was older than 1.5 seconds. This condition is there to prevent recreating the light network, when you have Smart Bike Lights on two data screens one after another and you scroll back and forth between the two. 

    You can force to switch back to N (Network) mode by calling restoreHeadlightsNetworkModeControl and restoreTaillightsNetworkModeControl methods:

    https://github.com/maca88/SmartBikeLights/blob/master/Source/SmartBikeLights/source-preprocess/BikeLightsView.mc#L2013