[Question] No stupid questions - ask anything here
-
@Thibault-B. You can catch long press with
onLongPressinpushButtonelement. 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 catchonLongPressStartand implement your own progress ring view, but that would probably be difficult to implement. -
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> -
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

-
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
-
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?
-
Tried to develop my own data screen.

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?
-
@matram How did you do this dual gauge-display? looks nice and I would be interested in learning more?
-
@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…

-
@matram Fantastic! many thanks!
-
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 viewtp.html- pace-focused viewth.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.jshandles the event inonEvent()main.jsupdatescurrentTemplatemain.jscallsunload('_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 theupbutton - removed a full-screen black root background
- changed the root element id from a generic
suuntoplusto 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')fromonEvent() - template reloading during the same button event
Questions:
- Is using the upper button for SuuntoPlus template switching expected to conflict with active navigation or POI notifications?
- Is
type="lock"supposed to prevent this kind of system overlay interaction, or does it only affect button lock behavior? - Is calling
unload('_cm')directly insideonEvent()considered safe during active navigation? - Would it be better to defer the actual
unload('_cm')to the nextevaluate()cycle after receiving the button event? - Is
uiViewSet/navigate()preferred over template switching for this case, even if it increases active UI memory pressure? - Is there a recommended button for cycling SuuntoPlus views during navigation, for example
downornextinstead ofup?
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.