Invalid value in Field.setData

I started to get some ERA errors like this:

Error Name: Invalid Value
Devices:
    Venu® Sq: 5.10
    Forerunner® 965: 21.22
Backtrace:
    MyView.compute:303

As far as I understand from the docs ActivityInfo.currentLocationAccuracy is supposed to be Position.Quality or Null, meaning only values: Null, 0,1,2,3,4 will ever be there. How come the below code produces Invalid Value error?

The relevant code is:

class MyView {
  var gpsAccuracy as Number or Position.Quality = 0;
  var gpsAccuracyField as Field?;

  initialize() {
    if (isGpsEnabledByUser) {
      gpsAccuracyField = session.createField("gpsAccuracy", 8, Fit.DATA_TYPE_UINT8, { :mesgType => Fit.MESG_TYPE_RECORD, :units => "" });
    }
  }

  function compute(info as Activity.Info) as Void {
    var gpsAccuracy = ifNullThenZero(info.currentLocationAccuracy) as Number;
    if (gpsAccuracy != self.gpsAccuracy) {
      self.gpsAccuracy = gpsAccuracy;
      if (gpsAccuracyField != null) {
        gpsAccuracyField.setData(gpsAccuracy); // line 303
      }
    }
  }

  function onTimer() as Void {
    var info = Activity.getActivityInfo();
    if (info != null) {
      compute(info as Activity.Info);
    }
  }

  function ifNullThenZero(val as Number or Float or Null) as Number or Float {
    return val != null ? val : 0;
  }
}

  • No one wrote yet so I'm going to say what I think.

    Not confident, but I usually don't use same-spelled variables in different levels of class, so maybe that's the issue?  perhaps line 303 gpsAccuracy is actually referencing class variable self.gpsAccuracy, (which you don't null check, you just check to ensure it's different than the local variable gpsAccuracy).  How could it be null since on the previous line you assigned it to whatever the value in local variable gpsAccuracy is?  I have no idea, but you didn't check for that so maybe it's possible.  You could test it by printing the values of both of those variables before you call setData, or test a 'devils advocate' condition and initialize local var gpsAccuracy as null on the line above where you're currently defining it to see if it changes to 0 as you would expect it to after you call ifNullThenZero (maybe initializing it as null would not work)

    I probably overkill checking for nulls, but if in line 299 you use info.currentLocationAccuracy, but what if info itself is null?  Would that possibly result in unexpected results when you intended to use currentLocationAccuracy? (oh, wait, you checked it for null before passing it to compute, nevermind!)

    My uncertainty probably betrays a lack of duck-typing skill haha... I am totally not familiar with how you've declared gpsAccuracy and gpsAccuracyField at the start of your code using "as" and "or" and I did not expect that question mark after 'field'... but I guess those declarations are legit based on no errors there!  haha okay, sorry that's all I got

    ---- edit:

    thanks a lot for the explanation!  I'm learning a lot!

  • perhaps line 303 gpsAccuracy is actually referencing class variable self.gpsAccuracy

    That shouldn't be possible and it would be a serious bug if it was so.

    If the same name exists at different scopes (e.g. global, class, and local), then when you use that name, the compiler/runtime should first reference the current scope (in this case, local). If the name isn't found in the current scope, the search should expand "outwards", going from the current scope to less specific scopes (i.e. local is more specific than class, which is more specific than global).

    That's why the self keyword is necessary in that context - to avoid referencing the local gpsAccuracy variable.

    Even if the class variable was being referenced, I don't think it would explain what's being seen.

    My uncertainty probably betrays a lack of duck-typing skill haha... I am totally not familiar with how you've declared gpsAccuracy and gpsAccuracyField at the start of your code using "as" and "or"

    Technically speaking, these type declarations aren't a feature of duck-typed languages per se, but a static compile-time type checker built on top of a duck-typed languages.

    e.g. Monkey Types is to Monkey C as TypeScript is to Javascript.

    JavaScript is also a language that is duck-typed, and TypeScript is essentially a type-checked layer "on top of" JavaScript. One relevant similarity to Monkey Types / Monkey C is that type checking only applies at compile time, which is why type casts only have an effect at compile time.

    Looking into TypeScript might be helpful (and interesting), as Monkey Types seems to take a few cues from TypeScript.

    var gpsAccuracy as Number or Position.Quality = 0;

    ^ This is a variable declaration with a type declaration which informs the type checker that gpsAccuracy must be constrained to be only a Number or Position.Quality (an enum). The type checker should return an error if it detects that any other kind of value is assigned to gpsAccuracy in the program.

    I did not expect that question mark after 'field'...

    var gpsAccuracyField as Field?;

    ^ This is just shorthand for:

    var gpsAccuracyField as Field or Null;

    This means that gpsAccuracyField can either be an instance of the Field class, or it can be null. ("Null", with a capital N, is the null type, while "null", with a lowercase N, is the null value.

  • I don't see anything wrong with the code on the surface.

    It does seem that if info.currentLocationAccuracy is in fact anything other than a Number or Null, then that would explain the crash (e.g. say it's somehow a Float or a String). But ofc that shouldn't be possible, as currentLocationAccuracy is documented to be Position.Quality or Null, and Position.Quality only consists of integer values between 0 and 4.

    Another crazy possibility could be that the value is a Number but it's less than 0 or greater than 255. Maybe -1 would be the most likely possibility here (out of a very unlikely set of possibilities)?

    Yet another possibility could be a bug in setData()?

    I know this is super obvious advice, but if you can recreate this problem on your own FR965, then logging the actual value of currentLocationAccuracy would probably give you the answer.

    And yeah, I realize that if you could recreate this problem, you probably wouldn't  be asking about it here. (At the very least you would be telling us what the problem is instead.)

  • Yeah, obviously out of ~100 activities I recorded I haven't seen this crash. Maybe it only happens when you ride a GPS satellite...

    I am thinking about adding stupid Garmin-proofing "debug" workaround,  something like:

    if (GPS accuracy<0) {tooSmall(gpsAccuracy)}

    else if (GPS accuracy>4) {tooBig(gpsAccuracy)}

    else {gpsAccuracyField.setData(gpsAccuracy)}

    and both tooSmall and tooBig would also call gpsAccuracyField.setData(gpsAccuracy)

    The idea is to get a different stack trace and then report it to Garmin (for them not to fix it as usually...)

  • You could also check to see if currentLocationAccuracy is a Float or something (or alternatively that’s it’s *not* null or Number). It would be crazy if the value was something like 0.0 instead 0, in the cases when it’s crashing.

  • Actually, this makes no sense even more, considering that the parameter of setData is Object and not Number, so if the value is Float and I declared the field as UINT8, then IMHO it would not crash in this line, but somewhere "inside" (though maybe this is the way it's reported)

  • There is a serious bug right now related to GPS data on Garmin devices. I don't think that is the issue here, but look into that. Watches and some EDGE devices are crashing. DC Rainmaker wrote about it.

  • Yes.  From the f8 device forum.  It was a corrupt GPS file sent out to a bunch of different devices.  For some, a master reset is needed.

    "We are aware of an issue causing some devices to be stuck on the start-up screen or a blue triangle.  To resolve this, press and hold the power button until the device turns off, then power it back on, and sync with the Garmin Connect app or Garmin Express.  If this does not resolve your issue, please click here for more information."

  • Ahhh, yes I saw it, I think you're right, maybe that could cause this error. I'll wait with my debug-hack and see if more errors will be reported

  • Actually, this makes no sense even more, considering that the parameter of setData is Object and not Number, so if the value is Float and I declared the field as UINT8, then IMHO it would not crash in this line, but somewhere "inside" (though maybe this is the way it's reported)

    By the same logic, there’s no way that specific line (with setData()) can crash at all, especially not with an “Invalid Value” error.

    Consider the ways for that line to crash:

    gpsAccuracyField is null or not a Session.Field: impossible (and should not cause an “Invalid Value” error anyway)

    - gpsAccuracyField does not have setData: also impossible (and “Invalid Value” does not apply)

    - gpsAccuracy is anything at all other than an integer between 0 and 4: who cares, how would that prevent setData() from being called? (same logic as your logic)

    It was my assumption from the start that setData() itself is crashing, not the call to setData()

    When I see an Invalid Value error in CIQ, it always seems to be caused by code inside an API function which doesn’t like the value/type of an argument which was passed in.

    considering that the parameter of setData is Object and not Number,”

    I could be wrong, but I don’t think parameters are type-checked at runtime, at the point of the function call. If an API function is typed to receive a Number and you pass in a String, for example, then I think it’s code inside the function which will cause an error (if any error is returned).

    Somewhat anecdotal evidence for this: Storage.setValue() is incorrectly typed and documented so that it doesn’t take a BitmapReference, but in fact, it does accept a BitmapReference.

    If parameters were typed-checked at the point of the function call, you would expect the call to setValue() to crash if it’s passed a BitmapReference.

    More anecdotal evidence: my whole argument above (“Invalid Value” should be impossible at the point of the function call to setData()).

    There is a serious bug right now related to GPS data on Garmin devices. I don't think that is the issue here, but look into that

    This is actually a great point and not that implausible.

    If the bug is serious enough to make devices crash, maybe part of the bug is that currentLocationAccuracy is corrupt in a way that the value isn’t a UINT8 (or to be more specific, isn’t compatible with setData for a UINT8 field).

    Ofc the ERA won’t tell us if the device is also rebooting / crashing right after the app crashes. I bet that’s exactly what’s happening here, so the app crash / ERA report is the least of their worries.