Suunto app Forum Suunto Community Forum
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Register
    • Login

    [Question] No stupid questions - ask anything here

    Scheduled Pinned Locked Moved Suunto Plus Development
    144 Posts 40 Posters 15.2k Views 39 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • M Online
      matram @Tomas5
      last edited by

      @Tomas5
      Trying to do the same thing here, with the same result. Works OK in simulator, not on RACE S.

      #3384733 28.05.2026 18:55:06 : ERR WBMAIN : *0: app 717 Event 37 80915318
      #3384734 28.05.2026 18:55:06 : ERR WBMAIN : 1: mea 79 Wait 0 00000000
      

      Some clues. Initially this manifested as a problem with low heap memory after only a few second of HR data.

      #3368541 28.05.2026 13:12:03 : ERR APPLICATION : Zapp:relMemCb (exec:ui)
      #3368542 28.05.2026 13:12:03 : ERR APPLICATION : Zapp:RelMem->None avail
      #3368543 28.05.2026 13:12:03 : ERR DUKTAPE : JSalloc:16
      

      That was fixed by using a Uint8Array(400) and pruning data. That removed the “relMemCb” fault but instead I got the “ERR WBMAIN”…

      It appears some resource is exhausted, and the watch becomes very sluggish after a few minutes. UI is almost frozen.

      Inserting some systemEvent() calls did not reveal any specific line in the code as the culprit.

      I tried performance.memory.usedJSHeapSize, but that is not supported it seems.

      It would be good to have more information about available memory (or other limited resources) and some guidance on how to interpret the error messages.

      Have a new Race2 on its way to me, will check if that makes any difference.

      1 Reply Last reply Reply Quote 0
      • Łukasz SzmigielŁ Offline
        Łukasz Szmigiel
        last edited by Łukasz Szmigiel

        Play stupid games, win stupid prizes xd

        catastrophicfailure.jpg

        TIL: the Duktape JS heap is 133,120 bytes (~130 KB), shared across all loaded zapps. At last in S9PP that is.

        Learned this the hard way today — Constantin crashed my watch three times, once into a full firmware restore.

        I merged my three view templates into one to fix a button-lock bug during route navigation. Bad idea. Each template is ~30–45 KB; all three loaded simultaneously at onLoad = ~130 KB = 99.4% of the entire heap before ZoneSense even shows up. The system tries to recover by force-unloading the offending zapp, which then reloads, fills the heap again, repeat, until Duktape can’t allocate 1392 bytes and crashes:

        WRN UI_FRAMEWORK : JsTotMem 132296/133120
        ERR APPLICATION : Zapp: releaseMemoryCb → Zapp 3: ReleaseMem -> unload  [×3]
        ERR APPLICATION : Zapp: ReleaseMem -> None avail.
        ERR DUKTAPE : JSalloc:1392  [×11]
        ERR FAULT : A302:duktapeFunctionCombiner.cpp
        EVT BOOTLOOP : Bootloop detected, sysmode 5->17
        

        Good to know: ≤ 30–40 KB per feature zapp, one template at a time. With ZoneSense (~30 KB) and Weather (~15 KB) running alongside, headroom is very tight.

        The watch is fine. I’ve reverted to the multi-template build. The original nav overlay bug is still open — if anyone’s solved the unload/type="lock" gap problem without blowing the heap, I’d love to hear it.

        S9PP 2.50.28

        1 Reply Last reply Reply Quote 1
        • Łukasz SzmigielŁ Offline
          Łukasz Szmigiel
          last edited by

          How can I read current wind gusts in a SuuntoPlus app?

          Hi @suuntopartnerteam,

          I’m working on a SuuntoPlus app that displays current weather and forecast weather data. I’m trying to show wind gusts for the current weather, but on the actual watch I only get --.

          In my template I currently read current weather like this:

          $.get('/Weather/Current', function(v) {
            gWxNow = v;
            renderNow();
          });
          

          and then I try to render current gusts with:

          setText('#nowWg', windStr(v.windGust));
          

          This works only if /Weather/Current returns a windGust field. On the watch, however, this seems to be undefined, so my formatter returns --.

          What confused me is that in the SuuntoPlus VS Code simulator internals, the weather mock seems to return a richer weather object. In suunto-internals, getWeatherData() includes fields such as:

          {
            humidity: 43,
            feelsLike: 293.15,
            temp: 293.15,
            weatherType: 2,
            windDeg: 230,
            windType: 5,
            windGust: 8,
            windSpeed: 6,
            pop: .23,
            rain: 1.2
          }
          

          So in the simulator, v.windGust can exist on the weather object.

          But from the documented data paths, /Weather/Current seems to expose only something like:

          {
            temp,
            weatherType,
            windSpeed,
            windType
          }
          

          and the listed current paths appear to be:

          /Weather/Current
          /Weather/Current.temp
          /Weather/Current.weatherType
          /Weather/Current.windSpeed
          /Weather/Current.windType
          

          I can read forecast gusts separately with a path like:

          /Weather/Future/N.windGust
          

          or at least this seems to work on the watch for forecast data. But I have not found an equivalent reliable path for current wind gusts, such as:

          /Weather/Current.windGust
          

          My main question is: is there any supported way to read current wind gusts in a SuuntoPlus app on the actual watch?

          I also have a related question about forecast index 0.

          What is the intended meaning of:

          /Weather/Future/0
          

          How is /Weather/Future/0 different from /Weather/Current?

          Is Future/0 the nearest hourly forecast slot, the current forecast hour, or something else? I’m asking because if current gusts are not available, one possible fallback would be to use /Weather/Future/0.windGust, but I’m not sure whether that would be semantically correct.

          More specifically:

          1. Does /Weather/Current.windGust exist on hardware?
          2. Should current gusts be read from another path, for example Observation data?
          3. Is windGust only available for forecast data, not current weather?
          4. Is the richer weather object in the VS Code simulator only mock data and not representative of the real watch API?
          5. What exactly is the difference between /Weather/Current and /Weather/Future/0?

          Right now, on the watch, current weather gusts display as --, while forecast gusts can be displayed.

          Tested on hardware, not only in the simulator. The issue is that the simulator mock appears to include windGust in the current weather object, but the actual watch does not seem to expose that field.

          Thanks for any clarification.

          S9PP 2.50.28

          1 Reply Last reply Reply Quote 0
          • withManishW Offline
            withManish
            last edited by

            I have a couple of questions.

            1. Is there a way to copy the built-in workout to a new workout while keeping the modified personal workout settings?

            I do understand that the app does not allow modifying any of the in-built workouts, but it’s great to be able to copy them as new personal workout settings and modify them as you want, rather than creating them from scratch.

            1. In-built options for workouts on the watch must be able to modify and save for next use, especially for the battery. As of now, battery mode reverts to Performance, even though I don’t need it in my regular usage. This is an extra checkpoint you have to complete before you start your workout every time.

            2. Even if I change battery mode, it won’t let me change what display setting I want.

            3. The map gets disabled on the watch. The map is already downloaded on the watch; you are just showing me where I am. This does not make any sense to disable map display.

            By the way, I use Race S as of now. My old Ambit 3 still lets me modify every setting as needed. I don’t understand why I am forced to use a performance mood every single time, even though I don’t need to.

            www.withManish.com

            sky-runnerS 1 Reply Last reply Reply Quote 0
            • sky-runnerS Offline
              sky-runner Platinum Member @withManish
              last edited by

              @withManish said:

              The map gets disabled on the watch. The map is already downloaded on the watch; you are just showing me where I am. This does not make any sense to disable map display.

              The map is enabled only in Performance mode. According to discussions that I’ve seen in this forum, the cost of preloading loading map segments from the storage is such that if it was enabled in modes other than Performance, the battery use would still be almost the same as in Performance mode.

              @withManish said:

              I don’t understand why I am forced to use a performance mood every single time, even though I don’t need to.

              Honestly I don’t understand why anyone would want to use a battery mode other than Performance. We are talking about 30 hours in Performance mode vs. 40 in Endurance. For everyday training that doesn’t make much difference because the majority of the battery use comes from the smartwatch usage outside of activity, which comes close to roughly 10% per day with raise to wake display activation.

              Let’s just assume that someone trains 1 hour per day with GPS. When using Performance mode, in a week 70% of use will come smartphone mode and 23% from recording activities. And if someone uses Endurance mode - 17% of use would be from recording activities. So at the end, it is a difference of 93% vs. 87% - not a significant difference at all.

              When I used Race S I didn’t bother and just used the Performance mode every single time. The only case when I’d consider Endurance mode is if I was doing a 100 mile ultramarathon with Race S.

              Suunto: Ambit, Ambit 3 Peak, 9 Baro, Race S, Race Ti, Vertical 2 Ti
              Garmin: Forerunner 210, Forerunner 610, Fenix 6X, Fenix 7X Ti

              withManishW 1 Reply Last reply Reply Quote 0
              • withManishW Offline
                withManish @sky-runner
                last edited by

                @sky-runner Thanks for explaining battery usage and map/GPS, but every % of power counts in remote places, where charging would be an issue, unlike at home.

                If loading maps consumes the same amount of power even though they are preloaded on the device, what is the point of having them preloaded? It is important that you are not always on the map; at times, check where your location is or where you are heading.

                So it might use a little more battery while checking maps, and again, back to endurance mode to gain more power backup. My question is: why forcibly disable instead of giving a prompt and letting them continue with the map? It’s a matter of safety, too; you want to know whether you’re where you are or headed in the right direction.

                And an important question: where is all this information mentioned in the manual? When I asked this question to a support person on chat, there was silence, and no response came for a very long time.

                Anyway, nice to get a response from you and make me understand.

                www.withManish.com

                1 Reply Last reply Reply Quote 0
                • D Offline
                  DonTomGot @matram
                  last edited by

                  @matram I tried including your brilliant design - but - I am very new to Javascript (all modern languages in fact). What pieces of code are supposed to go where in terms of .js files etc?

                  M 1 Reply Last reply Reply Quote 0
                  • M Online
                    matram @DonTomGot
                    last edited by

                    @DonTomGot
                    No problem, I am more of a Swift guy myself.

                    The functions arcSegment, drawGaugeBackground, pointer and renderGauges are declared as in the onLoad method in t.html.

                    onActivate is also in t.html, same as the onLoad method.

                    The canvas declaration goes at the end of the HTML code in the same file.

                    Good luck 👍

                    D 1 Reply Last reply Reply Quote 0
                    • D Offline
                      DonTomGot @matram
                      last edited by DonTomGot

                      @matram Many thanks for taking the time in helping out. Even the very basics is a puzzle from my background/my low level programming skills; but I am not giving up.

                      I included your code in the html-file, but understand there is also some logic needed - but cannot really figure out what variables that need to be controlled from the .js side of things? I have understood that PaceTarget is critical in your implementation, but outside of that I cannot really see what I might be missing? (I cannot get it to render in the simulator, and it appears your screen is from the simulator - so I suppose it should work also in simulator?)

                      To provide some more info: I have included the mentioned functions in the html (onLoad / onActivate) and the canvas declaration. But no results in the simulator.

                      M 1 Reply Last reply Reply Quote 0
                      • M Online
                        matram @DonTomGot
                        last edited by

                        @DonTomGot
                        This may be too complex as a first project. But let me start with giving you the overall structure of the code as that seems to be the problem at this point.

                        Manifest.json declares what input you need from the watch firmware, what data you intend to output from main.js to t.html and settings you use in the watch. This is parts of manifest.json

                        "in": [
                            {
                              "name": "Power",
                              "source": "/Activity/Move/-1/Power/Current",
                              "type": "subscribe"
                            },
                            {
                              "name": "HeartRate",
                              "source": "/Activity/Move/-1/HeartRate/Current",
                              "type": "subscribe"
                            },
                            {
                              "name": "Speed",
                              "source": "/Activity/Move/-1/Speed/Current",
                              "type": "subscribe"
                            },
                            {
                              "name": "SpeedAvg",
                              "source": "/Activity/Lap/-1/Speed/Avg",
                              "type": "subscribe"
                            }
                          ],
                          "out": [
                            {
                              "name": "PaceTarget"
                            },
                            {
                              "name": "TimeRemaining"
                            },
                            {
                              "name": "Debug"
                            }
                          ],
                          "template": [
                            {
                              "name": "t.html"
                            }
                          ],
                          "settings": [
                            {"shownName": "Repeat count", "path": "appSettings.repeatCount", "type": "int", "min": 1, "max": 19},
                        
                            {"shownName": "Target pace for WarmUp (m/s)", "path": "appSettings.targetPaceWarm", "type": "float", "min": 1, "max": 6},
                            {"shownName": "Target pace for Interval (m/s)", "path": "appSettings.targetPaceInterval", "type": "float", "min": 1, "max": 6},
                            {"shownName": "Target pace for Recovery (m/s)", "path": "appSettings.targetPaceRecovery", "type": "float", "min": 1, "max": 6},
                            {"shownName": "Target pace for CoolDown (m/s)", "path": "appSettings.targetPaceCool", "type": "float", "min": 1, "max": 6},
                        
                            {"shownName": "Duration for WarmUp (s)", "path": "appSettings.durationWarm", "type": "int", "min": 1, "max": 14400},
                            {"shownName": "Duration for Interval (s)", "path": "appSettings.durationInterval", "type": "int", "min": 1, "max": 14400},
                            {"shownName": "Duration for Recovery (s)", "path": "appSettings.durationRecovery", "type": "int", "min": 1, "max": 14400},
                            {"shownName": "Duration for CoolDown (s)", "path": "appSettings.durationCool", "type": "int", "min": 1, "max": 14400}
                          ]
                        

                        The main.js contains declarations for some variables

                        // App settings, see also manifest and data.json
                        var settings;
                        var isPaused;
                        
                        // State machine
                        var currentState = 'WarmUp';
                        var currentRepition = 1;
                        var repeatCount = 3;
                        var timeRemainingInStep = 10;
                        var currentPaceTarget = 2.326;
                        
                        // Average pace and max HR for last lap
                        var lastLapAvgPace;
                        var lastLapPeakHR;
                        

                        There is also a number methods that deal with the state machine needed for interval training. I give only their declarations and purpose for now.

                        // Function to translate speed to a pace string
                        var speedToPace = function(speed) 
                        
                        // Training session State Machine
                        // Get next step, set remaining time
                        // And increment interval counter
                        var setNextState = function()
                        
                        // Function to get target pace for current state
                        var getTargetPace = function() 
                        
                        // Function to set text for the top right text block
                        var setTextBlock = function() 
                        
                        // Update last lap data, avg pace and peak HR
                        var updateLastLap = function(input, output)
                        

                        The evaluate method may be of interest. Here it comes

                        // System starts calling this about once per second after the sports app is selected
                        // i.e. before the exercise is actually started.
                        // input: contains resources specified in "in" section of the manifest.
                        // output: resources passed to the device (specified in "out" section of the manifest).
                        function evaluate(input, output) {
                        
                          // If we are in an Interval step remember average pace and max HR from this step
                          updateLastLap(input, output)
                        
                          // Count down time remaining in current step
                          if (!isPaused) {
                            timeRemainingInStep -= 1;
                        
                            // Handle the state transition if time remaining is zero
                            if (timeRemainingInStep === 0) {
                        
                              // Trigger a silent lap
                              // $.put('Activity/Trigger', 24);
                        
                              // Set the next state
                              setNextState();
                        
                            } // End of if time remaining is 0
                        
                            // Output remaining time to HTML after the new durations is set
                            // If we are done and countdown is negative display positive value
                            output.TimeRemaining = (timeRemainingInStep < 0) ? -timeRemainingInStep : timeRemainingInStep;
                          } // End of if not paused
                        
                          // Get target pace for current step
                          getTargetPace();
                        
                          // Output target to HTML
                          output.PaceTarget = currentPaceTarget;
                          
                          // Update the top right text block
                          setTextBlock();
                        }
                        

                        Settings are loaded in onLoad and defaults provided, like this

                        // main.js loaded and system starts calling evaluate()
                        function onLoad(input, output) {
                          // Load app settings
                          settings = localStorage.getObject("appSettings");
                        
                          // Create default settings if none found
                          if (settings == null) {
                            settings = {
                             targetPaceWarm: 2.326,
                              targetPaceInterval: 2.857,
                              targetPaceRecovery: 2.083,
                              targetPaceCool: 2.083,
                        
                              durationWarm: 10,
                              durationInterval: 12,
                              durationRecovery: 5,
                              durationCool: 20,
                        
                              repeatCount: 2
                            }
                          }
                          isPaused = true;
                        }
                        

                        The bulk of this code deals with interval training and not the gauges, so if you are only interested in the gauges, you do not need most of the things above.

                        In t.html the code should go inside quotes in the onLoad and onActivate methods, the beginning looks like this.

                        <uiView
                        onActivate = "
                          $.subscribe('/Dev/Time/Tick10hz', function(){ control('#cnv', 'REFRESH'); })
                        
                          // Subscribe to HR, pace and targets to use in drawing gauges in the canvas
                          $.subscribe('/Activity/Move/-1/HeartRate/Current', function(v) { currentHR = parseFloat(v) * 60; })
                          $.subscribe('/Activity/Move/-1/Speed/Current', function(v) { currentPace = parseFloat(v); })
                          $.subscribe('/Zapp/{zapp_index}/Output/PaceTarget', function(v) { paceTarget = parseFloat(v); })
                          "
                        onLoad="
                          // Subscribed values
                          var currentHR;
                          var currentPace;
                          var paceTarget;
                        
                          // Target zones
                          var hrZones = [0, 133, 144, 155, 165, 185];
                          var paceZones = [0, 2.299, 2.5, 2.703, 2.857, 5.556];
                        
                          // Radius of the display in pixels
                          var radius = 233;       
                        
                          // Zone colors (muted, suitable as gauge backgrounds)
                          var z1Color = '#5B8FA8';    // Zone 1 - blue
                          var z2Color = '#6AA87A';    // Zone 2 - green
                          var z3Color = '#C4B050';    // Zone 3 - yellow
                          var z4Color = '#C87D45';    // Zone 4 - orange
                          var z5Color = '#B85555';    // Zone 5 - red
                          var zoneColors = [z1Color, z2Color, z3Color, z4Color, z5Color];
                        
                          // Other colors
                          var outColor = '#B85555';   // Out of target range (low and high sectors)
                          var inColor = '#6AA87A';    // Target range (mid sector)
                          var inactiveColor = '#999999'  // Inactive sector
                          var ptrColor = '#CCCCCC'    // Gauge pointer
                          var bkgColor = '#000000'    // Black background
                        
                          // For debugging 
                          var temp1 = '-';
                          var temp2 = '-';
                        
                          // Functions to draw gauges on canvas ----------------------------------
                        
                          // Draw an arc for a gauge
                          // from   from angle in degrees
                          // to     to angle in degrees
                          // color  arc color
                          // width  width of the arc
                          function arcSegment(ctx, from, to, color, width) {
                            ctx.beginPath();
                            ctx.arc(
                              radius, radius,          // center of display
                              radius - 2 - width / 2,  // radius - leave a small margin outside arc
                              Math.PI * from / 180,    // Angles from - to in degrees converted to radians
                              Math.PI * to / 180   
                            );
                            ctx.strokeStyle = color;
                            ctx.lineWidth = width;
                            ctx.stroke();
                          }
                        --- snip ---
                        

                        Apart from the code for rendering the gauge background and pointer which you already have above, you also need methods to identify the current zone to highlight.

                        // Get the index of the the active zone for a 3 zone target gauge
                          // value      current value
                          // targetLow  low end of target range
                          // targetHigh high end of target range
                          function getActiveTarget(v, targetLow, targetHigh) {
                            if (!isFinite(v)) return 0;
                            if (v > targetHigh) {
                              return 2;
                            } else if (v > targetLow) {
                              return 1;
                            } else {
                              return 0;
                            }
                          }
                        
                          // Get the index of the the active zone for a 5 zone HR gauge
                          // v          current value
                          // zones      an array of 4-values defining the 5 zones
                          function getActiveZone(v, zones) {
                            if (!isFinite(v)) return 0;
                            for (var i = 1; i < zones.length; i++) {
                              if (v < zones[i]) return i - 1;
                            }
                            return zones.length - 1;
                          }
                        

                        The environment is very challenging to work in, you get very little support. Often the app just silently fails. A few tips.

                        • build up in small steps and commit frequently
                        • check thoroughly for typos and other mistakes that would be caught by other IDE
                        • in the simulator toggle on “developer tools” , that will reveal some mistakes
                        • insert systemEvent statements where you can put information in the event log
                        1 Reply Last reply Reply Quote 0

                        Hello! It looks like you're interested in this conversation, but you don't have an account yet.

                        Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.

                        With your input, this post could be even better 💗

                        Register Login
                        • First post
                          Last post

                        Suunto Terms | Privacy Policy