3

Hi fellow Garmin developers,

I have been trying to develop a direct messaging communication setup over BLE between my Android App and my connectIQ app (on Garmin Forerunner 230, SDK version 1.3.x). The goal here is that the Android app is collecting some data, and then pushing it to the watch app.

Following the details on the developer site, I have managed to get this to work, but there are a lot of dropped messages that don't get sent, and the watch receives fewer values than what is being sent.

On Android, I get this status (ConnectIQ.IQMessageStatus) = FAILURE_DURING_TRANSFER in my debug statements. '240' is the data being sent.

D/GarminMessenger: onMessageStatus: Message: 240, device: Forerunner 230, FAILURE_DURING_TRANSFER

This is my app code on the garmin:

SampleApp.mc

using Toybox.Application as App;
using Toybox.Communications as Comm;
using Toybox.WatchUi as Ui;
using Toybox.System as Sys;

var mailMethod;
var crashOnMessage = false;

var msg;

class SampleApp extends App.AppBase {

    function initialize() {
        AppBase.initialize();
        Sys.println("app-initialize()");

        msg = "0";

        mailMethod = method(:onMail);
        Comm.setMailboxListener(mailMethod);
        Sys.println("app-initialize(): mail box listener has been set");
    }

    // onStart() is called on application start up
    function onStart(state) {
        System.println("app-onStart()");
    }

    // Return the initial view of your application here
    function getInitialView() {
        Sys.println("app-getInitialView()");
        return [ new SampleAppView() ];
    }

    function onMail(mailIter) {
        var mail = mailIter.next();

        while(mail!=null) {
            Sys.println("app-onMail: received - "+mail);

            message = mail.toString();
            Ui.requestUpdate();
            mail = mailIter.next();
        }

        Comm.emptyMailbox();
    }

    // onStop() is called when your application is exiting
    function onStop(state) {
        System.println("app-onStop()");
    }   
}

class CommListener extends Comm.ConnectionListener {
    function initialize() {
        Comm.ConnectionListener.initialize();
        sys.println("commlistener-initialize");
    }

    function onComplete() {
        Sys.println("commlistener-onComplete: Transmit Complete");
    }

    function onError() {
        Sys.println("commlistener-onError: Transmit Failed");
    }
}

Any ideas on what could be causing this issue? I am performing all the necessary checks on the Android side to verify if the Garmin watch is paired and connected (&the app is open).

One reason this could be happening is that I am trying to send 1-2 data values (each with a ConnectIQ.sendMessage()) every second, so perhaps the Garmin device/BLE module does not support communication at that rate?

Thanks in advance for solutions and suggestions.

ndhaijaan
  • 309
  • 1
  • 7
  • 14
  • I even tried to send data from Android a rates lower than 1/sec, and still lose similar number of data points along the way. – ndhaijaan Dec 23 '16 at 20:18
  • I think it totally depends on watches device, SDK version and bluetooth on your phone. BLE requires Bluetooth 4.0. – Maxim Feb 10 '17 at 19:05
  • Just tested on Forerunner 735xt it has the same error... BlueTooth 4.0, Android 7.1.1... latest firmware in watches... SDK 2.2.x, registerForPhoneAppMessages is used instead of mailbox... data is delivered to devices successfully but status is FAILURE_DURING_TRANSFER – Maxim Feb 14 '17 at 02:24
  • @Maxim Interesting you mention that. Now when you say data is delivered successfully, do you mean ALL data is delivered or do you lose some other messages along the way? I have a theory that the failure status is seen when some messages take longer to deliver than usual, by which time it is already trying to send another message. Hence it is the 2nd or 3rd message that is actually lost. Just a theory and I have not seen this consistently. – ndhaijaan Feb 14 '17 at 15:54
  • Sometimes some message is skipped... but it happens not very often. But this request is specific (it is first after getInstance and so on). In any case it always sends back FAILURE instead of SUCCESS but data is always delivered! I think I have not enough info to make any strong assumptions. So maybe you lost BT connection between watches and phone. – Maxim Feb 14 '17 at 17:29

2 Answers2

1

I think that the Connect messaging system just gets into some broken state and then no messages will go through. What you could try is to set up the Mailbox listener in onStart method instead of initialize.

Also there is a new method to make the message reading a lot easier. It is still largely undocumented, but I got a word it will be documented with the next SDK release. However, it is already working on every ConnectIQ watch. The method is:

Comm.registerForPhoneAppMessages(method(:onMsg));

where in your callback method you do:

function onMsg(msg) {
    handleIncomingMessage(msg.data.toString());
}

or something similar. The input object msg is of class Toybox::Communications::Message probably (this is not documented yet).

jiroch
  • 414
  • 6
  • 19
  • I think registerForPhoneAppMessages requires v1.4.0, author said about 1.3.0. – Maxim Feb 10 '17 at 19:01
  • Correct, but currently all the ConnectIQ watches support 1.4.0., so I believe there's no point in supporting just the old firmware. – jiroch Feb 10 '17 at 20:35
  • Like @Maxim mentioned, I don't think I could use the `Comm.registerForPhoneAppMessages(method)`, but I honestly have not tried using it either. – ndhaijaan Feb 10 '17 at 21:54
  • 1
    @ndhaijaan With the latest firmware (7.10), Garmin FR230 supports SDK 1.4.1, so it's a good bet to try the method I suggested. I'm able to get the messages faster than 1/sec with it (at least on my vivoactive). Link to firmware: http://www8.garmin.com/support/download_details.jsp?id=9513 – jiroch Feb 11 '17 at 09:15
  • Thanks @jiroch. I'll be sure to check that out! – ndhaijaan Feb 13 '17 at 15:19
  • You're welcome! And also take note that the Mailbox API (with the onMail method) is going to be deprecated, so it's also future-proofing. – jiroch Feb 13 '17 at 19:31
0

So I posted a similar question on the Garmin developer forum here, and got a partial answer to my problem. Posting a summary from there.

What I was hoping to implement was something life the following:

Assuming the messages from Android are 1, 2, 3, 4, 5: I would like the app to do update the UI as the messages are received, in real-time like this:

app-onMail: received - 1
//update the UI
app-onMail: received - 2
//update the UI
app-onMail: received - 3
//update the UI
app-onMail: received - 4
//update the UI
app-onMail: received - 5
//update the UI

Instead, this happens

app-onMail: received - 1 
app-onMail: received - 2 
app-onMail: received - 3 
app-onMail: received - 4 
app-onMail: received - 5 
//update the UI 
//update the UI 
//update the UI 
//update the UI 
//update the UI

THE ANSWER

The framework polls to see if there are new, unread mail messages. If there are any, it invokes the application onMail() callback which consumes each message from the queue, and repeatedly sets a flag that indicates the UI needs to update. After the call returns, the framework checks the flag to see if the UI needs to be updated, and if so it calls onUpdate() for the active view.

As such, I could only display every message if I send messages from Android at 5sec intervals. I could not find a way to receive and display data at higher rates due to its message polling frequency.

My responder suggested maintaining a queue of mail items (or just a counter) and then handling the mail items between draws, like this:

class MyApp extends App.AppBase
{
    hidden var _M_messages;
    hidden var _M_count;

    function initialize() {
        AppBase.initialize();
        _M_messages = new [10];
        _M_count = 0;
    }

    function getInitialView() {
        return [ new MyView() ];
    }

    function onStart(params) {
        Comm.setMailboxListener(self.method(:onMail));
    }

    function onStop(params) {
        Comm.setMailboxListener(null);
    }

    function onMail(mailIter) {

        var mail = mailIter.next();
        while (mail != null) {

            // only track up to 10 messages
            if (_M_count < 10) {
                _M_messages[_M_count] = mail;
                ++_M_count;
            }
            else {
                break;
            }

            mail = mailIter.next();
        }

        Comm.emptyMailbox();

        startProcessingMessages();
    }


    hidden function startProcessingMessages() {
        if (_M_timer == null) {
            _M_timer = new Timer.Timer();
            _M_timer.start(self.method(:processOneMessage), 250, true);
        }
    }

    hidden function stopProcessingMessages() {
        if (_M_timer != null) {
            _M_timer.stop();
            _M_timer = null;
        } 
    }

    function getMessageCount() {
        return _M_messages;
    }

    function processOneMessage() {
        if (_M_count != 0) {
            --_M_count;
            var mail = _M_messages[_M_count];
            _M_messages[_M_count] = null;

            // process the message here

           Ui.requestUpdate();

           if (_M_count == 0) {
               stopProcessingMessages();
           }
        }
    }
}

class MyView extends Ui.View
{
    hidden var _M_app;

    function initialize(app) {
        View.initialize();
        _M_app = app;
    }

    function onUpdate(dc) {

        var mailMessages = _M_app.getMessageCount();

        // draw the number of mail messages
    }
}
ndhaijaan
  • 309
  • 1
  • 7
  • 14