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
    134 Posts 38 Posters 14.1k Views 37 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.
    • Raimo JärviR Offline
      Raimo Järvi @Thibault B.
      last edited by

      @Thibault-B. You can catch long press with onLongPress in pushButton element. And maybe Popup example in SuuntoPlus editor could work for toggling an option. But I don’t know how to get the progress ring. I don’t know if it’s part of the multi-sport popup, or if it’s generic functionality that can also be used elsewhere. In theory, you could catch onLongPressStart and implement your own progress ring view, but that would probably be difficult to implement.

      Pragmatic Programmer

      1 Reply Last reply Reply Quote 0
      • surfboomerangS Offline
        surfboomerang
        last edited by

        Can a CSS guru help me out?

        I want to show the current speed in the center of the screen. This works fine without a postfix, but as soon as I add a postfix, the speed fields shifts slightly to the left because both <span> elements are centered together.

        How do I center the first <span> element and put the postfix right of it?

            <div class="sp-d-s p-hc" style="top:calc(87% - 50%e)">
              <span>
                <eval input="/Activity/Current/Speed" outputFormat="{zapp_speed_format}" default="--" />
              </span>
              <span class="cm-mid sp-t-s" style="padding-top:6px; padding-left:4px">
                <postfix />
              </span>
            </div>
        

        Suunto Vertical Titanium Solar
        OnePlus Nord 4

        1 Reply Last reply Reply Quote 0
        • Timo HelferT Offline
          Timo Helfer
          last edited by Timo Helfer

          Hey guys, could someone make HRV reading work?

          I do not get the values, even tough the Suunto Smart Belt is conencted.

          In the SImulator it says “undefined” for /HrvRmssd

          3b15736a-9e26-4429-b7f4-d9b54347d637-image.png

          1 Reply Last reply Reply Quote 0
          • bhamp0B Offline
            bhamp0
            last edited by

            Hi guys!
            Is there any solution to get the NGP values?
            I tried two different syntaxes: /Activity/Move/-1/NormalizedGradedPace/Current and /Activity/Move/-1/NGP/Current but both are unrecognized.
            Thanks a lot 🙂

            1 Reply Last reply Reply Quote 0
            • bhamp0B Offline
              bhamp0
              last edited by bhamp0

              I tried to investigate a bit more about my own question.
              I’ve been able to find two files in SuuntoPlus Editor that are listing all the available ressources:

              • .vscode/extensions/suunto.suuntoplus-editor-1.42.0/node_modules/@suunto-internal/suuntoplus-tools/lib/project/resource-common.js <= for generic ressources, not depending on parameters <= big array
              • .vscode/extensions/suunto.suuntoplus-editor-1.42.0/node_modules/@suunto-internal/suuntoplus-tools/lib/project/resource.js <= for parametric ressources <= multiple regexes, still readable 🙂

              Can’t find anything related to NGP, so I might conclude this is not available?

              1 Reply Last reply Reply Quote 0
              • M Offline
                matram
                last edited by

                Tried to develop my own data screen.

                fc718e2e-4d56-48ae-a876-2fede0a9803a-image.png

                Nice experience so far, many thanks to the devs for making this possible.

                My question comes to the “big picture”. I want to do structured trading with my own data screen(s). Currently I am using intervals.icu although I have been hacking on my own FIT file editor. When intervals upload training session, I get an extra data screen from intervals which I cannot modify and I also have my normal data screens which are not aware of the structure of the training.

                Any hints on the best way forward to combine my data screen with structured training?

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

                  @matram How did you do this dual gauge-display? looks nice and I would be interested in learning more?

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

                    @DonTomGot
                    I did this with a canvas.

                    These are the building blocks for the gauges.

                    // 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();
                      }
                    
                      // Draw the gauge background segments
                      // segmentAngles      an array of angles dividing the range, 4 for 3-zone target, 6 for 5-zone model
                      // segmentColors      the color for each segment
                      // activeSegment      the index to the active segment, drawn thicker
                      function drawGaugeBackground(ctx, segmentAngles, segmentColors, activeSegment) {
                        // Margin needs separate treatment for top (negative angles) and bottom (positive)
                        var halfMargin = (segmentAngles[0] > 0) ? -1 : 1;
                    
                        // Loop over all segments
                        for (var i = 0; i < segmentColors.length; ++i) {
                          var ang1 = segmentAngles[i] + halfMargin;
                          var ang2 = segmentAngles[i + 1] - halfMargin;
                    
                          // Determine if segment is active or not
                          var width = (i === activeSegment) ? 14 : 5;
                          arcSegment(ctx, ang1, ang2, segmentColors[i], width);  
                        }
                      }
                    
                      // Draw a pointer for any gauge
                      // ctx    context
                      // angle  angle to draw arrow at, -90 top center, +90 bottom center
                      function pointer(ctx, angle) {
                        var ptrHeight = 25;     // Height of gauge pointer in pixels
                        var ptrAngle = 3;     // Half angle of gauge pointer in degrees
                    
                        ctx.beginPath();
                        var x1 = (radius - ptrHeight) * Math.cos(Math.PI * angle / 180) + radius;
                        var y1 = (radius - ptrHeight) * Math.sin(Math.PI * angle / 180) + radius;
                        ctx.moveTo(x1, y1);
                    
                        var x2 = radius * Math.cos(Math.PI * (angle - ptrAngle) / 180) + radius;
                        var y2 = radius * Math.sin(Math.PI * (angle - ptrAngle) / 180) + radius;
                        ctx.lineTo(x2, y2);
                    
                        var x3 = radius * Math.cos(Math.PI * (angle + ptrAngle) / 180) + radius;
                        var y3 = radius * Math.sin(Math.PI * (angle + ptrAngle) / 180) + radius;
                        ctx.lineTo(x3, y3);
                        ctx.fillStyle = ptrColor;
                        ctx.closePath();
                    
                        // Stroke with black, to create a margin around the arrow
                        ctx.strokeStyle = bkgColor;
                        ctx.lineWidth = 3;
                        ctx.fill();
                        ctx.stroke();
                      }
                    

                    And here the two gauges are rendered …

                    // The function redraws both top gauge for pace target bottom gauge for HR zones
                      var renderGauges = function(ctx) {
                    
                        // Top pace gauge - first background
                        var targetLow = 0.97 * paceTarget;
                        var targetHigh = 1.03 * paceTarget;
                        var segmentAngles = [-135, -105, -75, -45];
                        var segmentColors = [outColor, inColor, outColor];
                        var activeIndex = getActiveTarget(currentPace, targetLow, targetHigh);
                        drawGaugeBackground(ctx, segmentAngles, segmentColors, activeIndex);
                    
                        // Then draw the pointer
                        var paceAngle = -135;
                        if (typeof currentPace != 'undefined') {
                          var fPace = (currentPace - 0.5 * ( targetHigh + targetLow)) / (targetHigh - targetLow);
                          fPace = Math.min(Math.max(fPace, -1.5), 1.5);
                          paceAngle = -90 + fPace * 30;
                        }
                        pointer(ctx, paceAngle);
                    
                        // Bottom HR Zone gauge - background
                        var activeZone = getActiveZone(currentHR, hrZones);
                        var zoneAngles = getZoneAnglesHR(activeZone);
                        drawGaugeBackground(ctx, zoneAngles, zoneColors, activeZone);
                    
                        // Compute angle for HR pointer
                        var hrAngle = 135;
                        if (typeof currentHR != 'undefined') {
                          var fZone = (currentHR - hrZones[activeZone]) / (hrZones[activeZone + 1] - hrZones[activeZone]);
                          fZone = Math.min(Math.max(fZone, 0), 1);
                          var aLow = zoneAngles[activeZone];
                          var aHigh = zoneAngles[activeZone + 1];
                          hrAngle = aLow + fZone * (aHigh - aLow);
                        }
                        pointer(ctx, hrAngle);
                      }
                    

                    You will need to refresh the canvas and subscribe in .onActivate.

                    <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); })
                      "
                    

                    And of course add the canvas to HTML.

                    <!-- Canvas holds top pace gauge and bottom HR gauge -->
                        <object id="cnv" type="canvas"
                              build="ctx => renderGauges(ctx)"
                              style="position:absolute; left: 0px; width: 466px; top: 0px; height: 466px;" />
                    

                    This is my current iteration…

                    44d451ed-2007-4a78-a23e-1db49e99b53f-image.png

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

                      @matram Fantastic! many thanks!

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

                        Hi,

                        I’m seeing a reproducible issue with a SuuntoPlus app during active navigation, and I’d like to ask what the recommended mitigation is.

                        The app is called Constantin. It uses multiple UI templates for different workout views:

                        • td.html - drift / combined view
                        • tp.html - pace-focused view
                        • th.html - heart-rate-focused view

                        The switching logic follows the documented pattern from the SuuntoPlus examples:

                        • the HTML template only sends a /Zapp/{zapp_index}/Event
                        • main.js handles the event in onEvent()
                        • main.js updates currentTemplate
                        • main.js calls unload('_cm')
                        • getUserInterface() returns the selected template

                        Simplified version:

                        var currentTemplate = 'td';
                        
                        var changeTemplate = function(template) {
                          if (currentTemplate === template) return;
                          currentTemplate = template;
                          unload('_cm');
                        };
                        
                        function onEvent(input, output, eventId) {
                          if (eventId === 1) {
                            if (currentTemplate === 'td') changeTemplate('tp');
                            else if (currentTemplate === 'tp') changeTemplate('th');
                            else changeTemplate('td');
                            return;
                          }
                        
                          if (eventId === 2) {
                            if (currentTemplate === 'td') changeTemplate('th');
                            else if (currentTemplate === 'th') changeTemplate('tp');
                            else changeTemplate('td');
                          }
                        }
                        
                        function getUserInterface() {
                          return { template: currentTemplate || 'td' };
                        }
                        

                        In the templates I use a mechanical button like this:

                        <pushButton
                          name="up"
                          type="lock"
                          longType="lock"
                          onClick="$.put('/Zapp/{zapp_index}/Event', 1, null, 'int32');"
                          onLongPressStart="$.put('/Zapp/{zapp_index}/Event', 2, null, 'int32');"
                        />
                        

                        The UI itself is intentionally split into separate templates instead of one large uiViewSet, because earlier versions had UI/Duktape memory pressure on the physical watch, especially when running together with ZoneSense and navigation. Splitting into separate templates reduced active subscriptions, active evals, and canvas/UI load.

                        The problem:

                        When active route navigation is running, pressing the upper button to cycle templates causes a system navigation/POI notification to appear at the bottom of the screen. Sometimes it only flashes, but sometimes it stays visible indefinitely on top of the SuuntoPlus app.

                        The app itself continues to work. The template changes correctly. Data updates continue. But the system POI/navigation overlay remains visible.

                        The overlay disappears if I leave Constantin and switch to another screen/app, then return to Constantin. The problem appears only when cycling templates with the upper button while navigation is active.

                        Things already tested:

                        • removed delayed/postponed rendering from the UI
                        • removed postpone-rendering:true
                        • reduced canvas refreshes and subscriptions
                        • added type="lock" / longType="lock" to the up button
                        • removed a full-screen black root background
                        • changed the root element id from a generic suuntoplus to unique ids per template
                        • verified that the canvas is not full-screen and should not be covering the whole display

                        None of these mitigated the navigation/POI overlay issue.

                        So my current suspicion is that this is not caused by canvas rendering or a full-screen DOM element. It looks more like an interaction between:

                        • the physical upper button
                        • active navigation / POI system UI
                        • immediate unload('_cm') from onEvent()
                        • template reloading during the same button event

                        Questions:

                        1. Is using the upper button for SuuntoPlus template switching expected to conflict with active navigation or POI notifications?
                        2. Is type="lock" supposed to prevent this kind of system overlay interaction, or does it only affect button lock behavior?
                        3. Is calling unload('_cm') directly inside onEvent() considered safe during active navigation?
                        4. Would it be better to defer the actual unload('_cm') to the next evaluate() cycle after receiving the button event?
                        5. Is uiViewSet / navigate() preferred over template switching for this case, even if it increases active UI memory pressure?
                        6. Is there a recommended button for cycling SuuntoPlus views during navigation, for example down or next instead of up?

                        What I’m trying to achieve is a stable way to cycle between 3 lightweight SuuntoPlus workout views on the physical watch, while active navigation and another S+ app such as ZoneSense are running, without triggering or leaving behind the system navigation/POI overlay.

                        Any guidance on the recommended architecture or mitigation would be appreciated.

                        S9PP 2.50.28

                        1 Reply Last reply Reply Quote 0
                        • First post
                          Last post

                        Suunto Terms | Privacy Policy