So, according to ERA, a System Error was generated in the code below, but why?

So the following background running code generated a System Error on a Fenix 6X Pro at the "else if (responseCode == 408)". What makes this line different than the others? Why not the 401 line just above it???

var suffix;
if (responseCode == 200 && responseData != null) {
    :
    : reading responseData here, skipping it for the sake of simplicity here
    :
    
    suffix = "";
}
else if (responseCode == 401) {
    suffix = Application.loadResource(Rez.Strings.label_launch_widget);
}
else if (responseCode == 408) {
    suffix = Application.loadResource(Rez.Strings.label_asleep);
}
else {
    suffix = Application.loadResource(Rez.Strings.label_error) + responseCode;
}

All three string resources in the code above are defined with 'scope="background"' and the watch language was English. If I emulate this watch,it works flawlessly.

It's frustrating trying to make a program reliable when things like that happen :-<

  • You shouldn't use loadResource/storage/etc in onUpdate! - it kills battery.

    Maybe you should write a small function to save memory...

    function data2status(data, status, key)

    {

    value = data[key];

                if (value != null) {

                    status[key] = value;

                }

    }

  • Glance code aren't ran long normally, hence the name Slight smileso would the impact be negligible anyway? If not, there are only 10 strings, so I could load them into a dictionary in onShow and reference that dictionary instead. Would that help? My only issue is I'm already pretty tight in space on watches with just 32 KB of BG space. I'm afraid that could bring it over limit when that 7.5 KB of data is returned through that makeWebRequest and the famous 403 is returned instead of the 200 (with data).

    Edit: Thinking about it, in GlanceView:onUpdate, text is pretty static, so it shouldn't be an issue with preloading the strings if I have the room. But for MainView:onUpdate, the bitmaps/strings it shows are based on what the state of the vehicle is at that moment and that can change randomly/frequently, and there are a lots of options, but the field to display data on screen is limited, so what if I only load a resource if it's not already the one being displayed (ie, saved in class variable when loaded so I can reuse it until it needs to change)? Would that work?

  • Your decision but if you are near to memory limit you will start to optimize code soon:)

    If you need memory in background move most of code outside the app (outside :background annotation)!

    You need only additional func readStorageData to call after onBackgroundData() and onStart().

    If you have memory on stack to draw something it means you can save this data in member...

  • I've spent many hours optimizing, to the points that I now have :bkgnd32kb and :bkgnd64kb tags for background functions so those with just 32KB of memory have a completely different code with unfortunately less features (there is so much I can cram there). It also means I have to maintain two versions of many functions Disappointed The killer is makeWebRequest returns 7.5KB of data. When it's parsed as a dictionary, it balks every time. I have to read it as a string and picking what I need from it manually.

    if (responseCode == 200 && responseData != null) {
        var pos = responseData.find("battery_level");
        var str = responseData.substring(pos + 15, pos + 20);
        var posEnd = str.find(",");
        data.put("battery_level",$.validateNumber(str.substring(0, posEnd)));
    
        pos = responseData.find("battery_range");
        str = responseData.substring(pos + 15, pos + 22);
        posEnd = str.find(",");
        data.put("battery_range", $.validateNumber(str.substring(0, posEnd)));
    
        pos = responseData.find("charging_state");
        str = responseData.substring(pos + 17, pos + 37);
        posEnd = str.find("\"");
        data.put("charging_state", $.validateString(str.substring(0, posEnd)));
    
        pos = responseData.find("inside_temp");
        str = responseData.substring(pos + 13, pos + 20);
        posEnd = str.find(",");
        data.put("inside_temp", $.validateNumber(str.substring(0, posEnd)));
    
        pos = responseData.find("shift_state");
        str = responseData.substring(pos + 13, pos + 20);
        posEnd = str.find(",");
        var value = $.validateString(str.substring(0, posEnd));
        data.put("shift_state", (value.equals("null") ? "P" : value));
    
        pos = responseData.find("sentry_mode");
        str = responseData.substring(pos + 13, pos + 20);
        posEnd = str.find(",");
        data.put("sentry", $.validateString(str.substring(0, posEnd)).equals("true"));
    
        pos = responseData.find("preconditioning_enabled");
        str = responseData.substring(pos + 25, pos + 32);
        posEnd = str.find(",");
        data.put("preconditioning", $.validateString(str.substring(0, posEnd)).equals("true"));
    }
    

    Only way I can see I can optimize this more would be by reducing the name of the dictionary keys. The validate* functions makes sure the data is valid since I've got Unhandled Exception there.

    edit: This is the 64KB equivalent using dictionary

    if (responseCode == 200 && responseData != null) {
    	var response = responseData.get("response");
        if (response.get("charge_state") != null && response.get("climate_state") != null && response.get("drive_state") != null) {
            _data.put("battery_level", $.validateNumber(response.get("charge_state").get("battery_level")));
            _data.put("battery_range", $.validateNumber(response.get("charge_state").get("battery_range")));
            _data.put("charging_state", $.validateString(response.get("charge_state").get("charging_state")));
            _data.put("inside_temp", $.validateNumber(response.get("climate_state").get("inside_temp")));
            _data.put("shift_state", (response.get("drive_state").get("shift_state") == null ? "P" : $.validateString(response.get("drive_state").get("shift_state"))));
            _data.put("sentry", $.validateBoolean(response.get("vehicle_state").get("sentry_mode")));
            _data.put("preconditioning", $.validateBoolean(response.get("charge_state").get("preconditioning_enabled")));
        }
    }
    

  • I don't have time to deeply analyse the code but you can reduce memory usage in many ways. If you have problem with memory you have to find out why and then fix code...

    - move all code from :background to other part of app e.g. all code to save timestamp can be

    data["timestamp"] = Toybox.Time.now().value();

    rest in the view (no in the app)

    btw.using

    data.put("timestamp", Toybox.Time.now().value());

    takes 11bytes more memory, so 100x gives 1kb....

    - hide globals in view - all globals are null in background but take memory

    - remove empty functions e.g. onStart/onStop

    - look into memory monitor, print Toybox.System.Stats.freeMemory...

    -...

    Well, CIQ is ok when you don't meet out of memory exception...

    Good luck Slight smile

  • Yeah, I though about the timestamp, but don't the glance and background code shares the same 32KB of memory?So moving the timestamp code from the background to the glance view wouldn't help, right?

    I've trimmed all the code I could from the background and glance class. Like I said, using bkgnd32kb and bkgnd64kb tag, I even drop nice features from the 64KB code (like auto renew the access token when an 401 error is received) that would make the 32KB code too big.

    I'm using one global, how do you hide it from background space?

    The onStart/onStop habe been tagged with the bkgnd64kb tag so they are not in the 32KB space.

    Didn't know about Toybox.System.Stats.freeMemory, I'll look into it

    Thanks.

  • Unfortunately every line of your code eats memory... So move somewhere (and dont use try/catch without reason) and check  Toybox.System.Stats.freeMemory before and after:

    var timestamp;
    try {
    var clock_time = System.getClockTime();
    var hours = clock_time.hour;
    var minutes = clock_time.min.format("%02d");
    var suffix = "";
    if (System.getDeviceSettings().is24Hour == false) {
    suffix = "am";
    if (hours == 0) {
    hours = 12;
    }
    else if (hours > 12) {
    suffix = "pm";
    hours -= 12;
    }
    }
    timestamp = " @ " + hours + ":" + minutes + suffix;
    }
    catch (e) {
    timestamp = "";

    }

  • Thanks for the tool, it's helpful.

    Wow, the space taken by a dictionary is quite enormous!

    This is the free space for a 408 responseCode (responseData is null) and 200 (responseData has the returned data as a string) on a Fenix6X Pro

    408 => Background: Total memory: 28640 Used memory: 12568 Free memory: 16072
    200 => Background: Total memory: 28640 Used memory: 19344 Free memory: 9296

    So, 6776 bytes taken by responseData.

    Same thing on a Venu2, which reads responseData as a dictionary

    408 => Total memory: 61408 Used memory: 17184 Free memory: 44224
    200 => Total memory: 61408 Used memory: 35912 Free memory: 25496

    Ouch, 18728 bytes, 2.7 times more! No wonder I get 403 when trying to read that data as a dictionary on the Fenix6X Pro!

  • Pay attention to peak memory as when json data comes in and the dictionary is built there will be a spike in memory

  • Yeah, that's what the data above shows. Major spike in memory usage, hence why I had to extract the data manually.