Suunto app Forum Suunto Community Forum
    • Recent
    • Tags
    • Popular
    • Users
    • Groups
    • Register
    • Login
    1. Home
    2. Popular
    Log in to post
    • All Time
    • Day
    • Week
    • Month
    • All Topics
    • New Topics
    • Watched Topics
    • Unreplied Topics

    • All categories
    • K

      Nap detection off

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Vertical 2
      23
      0 Votes
      23 Posts
      1k Views
      K
      I just gave up with all the sleep and recovery tracking and use it as a normal watch. More batterylife and no stress with useless functions that work when ever they feel like
    • I

      Limited edition default watch face gone

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Vertical 2
      4
      0 Votes
      4 Posts
      340 Views
      I
      I did reset twice and still don’t get the the default Suunto Vertical 2 watch face no matter what I try. Also is not in the Suunto store. Any ideas?
    • Francesco PaganoF

      New strap - suggestions?

      Watching Ignoring Scheduled Pinned Locked Moved Suunto 9 Peak Pro
      4
      1
      0 Votes
      4 Posts
      180 Views
      Francesco PaganoF
      @Tami999 I guess it’s the effect of sunlight, or maybe that plus seawater ‍️ you can have it once I replace it
    • T

      Suunto Vertical Titanium Solar Malfunction Unresponsive After Water Exposure!!!

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Vertical
      12
      1 Votes
      12 Posts
      855 Views
      F
      @markman8 AFAIK it is not related to gaskets. It’s a software/hardware issue that was well reported in this forum. It has happened to me once (Summer 2024) and the watch was replaced under warranty. There are also some forum members that had multiple failures (and replacements). It was never disclosed what the actual underlying reason was but the failure is known and quite common. Suunto might not willingly acknowledge it but from a moral standpoint they should.
    • JebClydeNCJ

      Daylight Saving Time Woes

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Run
      4
      0 Votes
      4 Posts
      180 Views
      JebClydeNCJ
      @Brad_Olwin iPhone 15, latest firmware’s and App versions. I can’t help but wonder though, two things I know compared to other suunto watches: The software backend for the Run is completely different from others—evidenced by single-note tones (instead of the delightful “Suunto Song” for alarms), music, and plenty of other bits indicating its its own breed of software This is the first “spring forward” since the release of the Run—so it may be possible this bug hasn’t been noticed because it wasn’t possible before now?
    • Olli2709O

      no Foto Upload

      Watching Ignoring Scheduled Pinned Locked Moved Suunto app and other software services
      4
      1
      1 Votes
      4 Posts
      222 Views
      Olli2709O
      Resizing the photo didn’t work. It must be specific to certain photos and certain training sessions. The photo was 7.5 MB. I took the same photo again the next day and was then able to upload it. I’m a bit baffled. It doesn’t happen all the time, but it does occur occasionally.
    • G

      Suunto Run GoodMorning

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Run
      3
      0 Votes
      3 Posts
      49 Views
      JebClydeNCJ
      Perhaps slightly off topic, but adding HRV numbers to the report would be really nice—its the one data point that feels oddly missing from an otherwise fantastic feature!
    • Kraisun TuntaK

      Recovery watch & App

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Race 2
      21
      1 Votes
      21 Posts
      2k Views
      N
      @Thaler-Martin I come from Garmin, and Amazfit too. At the moment, my Race 2 is on par with Amazfit considering sleep tracking, but slightly more in line with my feelings. Clearly, Suunto is better, for me, at tracking sleep time and HR. HRV is quite equal. The worst is Garmin for me, considering sleep time accuracy and sleep score. HRV interpretation is also worse on Garmin (it takes too much time to adapt to new baseline fluctuation caused by seasons and training load, giving wrong alert in HRV status). Btw, I’m quite new with the Race 2, and have 2 nights to go before receiving HRV status.
    • W

      First run and newbie question

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Vertical 2
      3
      0 Votes
      3 Posts
      136 Views
      W
      @runsgrun thanks! Much appreciated.
    • withManishW

      Stuck to Power Save Mode

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Race S
      3
      1
      0 Votes
      3 Posts
      185 Views
      withManishW
      @Horizontal_2 Thank you, the trick that worked.
    • S

      Race 2 accuracy

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Race 2
      16
      0 Votes
      16 Posts
      1k Views
      2
      @shamilt1 Suunto uses something called FusedSpeed. It uses GPS and your steps to calculate your distance. So it is possible the calculation differs, especially as GPS coverage gets bad…
    • sky-runnerS

      Climb guidance 2.0 issue - not all zoom levels are available at the end of the route (see picture)

      Watching Ignoring Scheduled Pinned Locked Moved Watches
      19
      1
      5 Votes
      19 Posts
      2k Views
      sky-runnerS
      @Tieutieu said in Climb guidance 2.0 issue - not all zoom levels are available at the end of the route (see picture): @sky-runner the only missing thing ihmo is this extra zoom level when there is waypoints . In other cases climb guidance is perfect No, it is not perfect when doing a really long distance with a lot of waypoints, and that is what the above report is about. The zooming doesn’t work correctly when at the end of the route as it may not be possible to zoom in to the desired zoom level. Imagine that you run a 100 km race and let’s say there is an aid station at 5 km before the finish and another aid station at 10 km before the finish. And let’s say there are waypoints for all aid stations. And let’s say you are just past the second from the end aid station, so basically you have 10 km to go and one more waypoint before the finish. Please see the picture at the top of the post for the illustration of what I am trying to explain. At that point it is possible to see the entire profile for the entire 100km, but the elevation profile is so compressed horizontally that it is unusable. Also it is possible to zoom just at the 2nd from the end segment of the profile - from 10km before the finish to 5km before the finish. That is good. But it is impossible to zoom into the segment that shows the last 10 km - from 10km before the finish to the finish. Instead it goes straight into the entire 100km profile. That’s what the above report is about. A similar zoom level is available at the beginning of the route but not at the end. The logic is flawed!
    • M

      Main Watch Widgets in OpenSuuntoPlus

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Plus Development
      2
      2 Votes
      2 Posts
      89 Views
      2
      @MirageDev IMHO there is no such thing as widgets that can be installed outside of a firmware update. So, I guess there isn’t even infrastructure there, yet…
    • K

      How to disable 'the raise by hand" Vertical 1

      Watching Ignoring Scheduled Pinned Locked Moved Watches
      2
      0 Votes
      2 Posts
      75 Views
      sartoricS
      locking as duplicate
    • Neil McElroyN

      Race 2 - Importing of GPX files from OS mapping (UK) Elevation profile problems..

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Race 2
      5
      0 Votes
      5 Posts
      542 Views
      J
      I create routes in Caltopo. If I import directly into Suunto App I loose the elevation data. I import into GPS Visualizer.com first and the to Suunto it adds back in the elevation data.
    • O

      Why Is My Suunto Vertical 2 Having Connection and Power Data Recording Issues?

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Vertical 2
      2
      1 Votes
      2 Posts
      86 Views
      F
      @onghyr, yes, https://forum.suunto.com/topic/14749/bug-vertical-2-randomly-dropping-external-bt-sensors-and-not-reconnecting Unfortunately no response from the forum big guys (moderators, field testers).
    • Marek MachalaM

      Outback adventure watchface small design changes more readable

      Watching Ignoring Scheduled Pinned Locked Moved SuuntoPlus™ WatchFaces
      2
      3 Votes
      2 Posts
      186 Views
      martintrailM
      @Marek-Machala Hlavni mesto Praha is still fine. I have Roznov pod Radhostem there, and it almost doesn’t fit in there I would gladly delete the entire position and leave only the weather, as you write.
    • matt-rM

      Suunto Race S – stress and HR issues after charging (firmware 2.50.x)

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Race S
      3
      0 Votes
      3 Posts
      258 Views
      withManishW
      @matt-r, I have noticed that with this watch, HR and SpO2 readings are inconsistent and inaccurate, even after following the instructions during resting.
    • S

      Examples explained

      Watching Ignoring Scheduled Pinned Locked Moved Suunto Plus Development development
      2
      0 Votes
      2 Posts
      361 Views
      S
      FowlPlay FowlPlay is a quick, light‑hearted minigame perfect for killing time while waiting during any sport. Master the slingshot, tweak your trajectory and send your bird soaring toward its waiting pig friend. manifest.json The manifest for FowlPlay only defines the strictly necessary fields: name (that app’s name), description (a short description of the app; preferably under 22 characters), version (a unique short identifier; must be changed when reuploading to ApiZone), author (credit where credit is due), modificationTime (a Epoch Unix Timestamp in whole seconds representing modification time), type (the literal string “feature” for sport apps), usage (the literal string “workout”) and template (an array of html templates used by this app). manifest.json: { "name": "FowlPlay", "description": "Unite with the pig!!", "version": "1.0", "author": "Birdy B.", "modificationTime": 1770000000, "type": "feature", "usage": "workout", "template": [{"name": "g.html"}] } main.js For this app, all rendering and game logic live directly inside the HTML template (see below). Every app still needs a main.js file and must implement the global getUserInterface function that tells the watch which template to load. The function should return an object that includes at least a template field with template’s name. The file extension (.html) should be dropped here, even though the manifest lists full file names for templates. Here’s our minimal main.js file: main.js: function getUserInterface() { return { template: 'g' }; } g.html Finally, the HTML template! The file name should match the declaration in manifest.json and value returned by getUserInterface in main.js. For your convenience the entire code with comments is listed here and explained below: g.html: <uiView onLoad=" // Set constants for world gravity (g) and bird/enemy radius(r) var g = .2, r = 5; // Set the current state (one of 'sling', 'launched', 'won' or 'reset') and initialize sleep timer (s) var state = 'sling', s = 0; // Set variables for the launch force (f), launch angle (a) and current direction (d; angle) var f = 2, a = 0, d = 0; // Define the position and velocities for the bird and enemy var bird = {x: 20, y: 61.5, dx: 0, dy: 0}; var enemy = {x: 80, y: 80 - r, dx: 0, dy: 0}; /** @param { CanvasRenderingContext2D } ctx */ function renderGame(ctx) { // Viewport width and height from canvas width and height var vw = ctx.width / 100; var vh = ctx.height / 100; // Draw sky ctx.fillStyle = '#CCEEFF'; ctx.fillRect(0, 0, 100 * vw, 100 * vh); // Draw ground ctx.fillStyle = '#14B814'; ctx.fillRect(0, 80 * vh, 100 * vw, 20 * vh); // Draw slingshot ctx.strokeStyle = '#8A380F'; ctx.fillStyle = '#8A380F'; ctx.beginPath(); ctx.lineWidth = 3 * vh; ctx.lineCap = 'round' ctx.arc(20 * vw, 61.5 * vh, 7.5 * vh, 0, 3.15); ctx.stroke(); ctx.fillRect(18 * vw, 70 * vh, 4 * vw, 10 * vh); // Draw bird circle(ctx, '#F2DF0D', bird.x * vw, bird.y * vh, r * vh); // Calculate current direction d = (state == 'sling') ? a : (!!bird.dx ? (Math.atan(bird.dy / bird.dx) + .8) / -.2 : 0); // Draw beak ctx.fillStyle = '#F97706'; ctx.beginPath(); ctx.moveTo((bird.x + Math.cos(-d * .2 - 1 ) * r * .45) * vw, (bird.y + Math.sin(-d * .2 - 1 ) * r * .45) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - .4) * r * .95) * vw, (bird.y + Math.sin(-d * .2 - .4) * r * .95) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 + .2) * r * .45) * vw, (bird.y + Math.sin(-d * .2 + .2) * r * .45) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - .4) * r * .2 ) * vw, (bird.y + Math.sin(-d * .2 - .4) * r * .2 ) * vh); ctx.closePath(); ctx.fill(); // Draw eyes ctx.fillStyle = '#000000'; ctx.beginPath(); ctx.moveTo((bird.x + Math.cos(-d * .2 - 2.1) * r * .25) * vw, (bird.y + Math.sin(-d * .2 - 2.1) * r * .25) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.4) * r * .5 ) * vw, (bird.y + Math.sin(-d * .2 - 1.4) * r * .5 ) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.5) * r * .75) * vw, (bird.y + Math.sin(-d * .2 - 1.5) * r * .75) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.2) * r * .75) * vw, (bird.y + Math.sin(-d * .2 - 1.2) * r * .75) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.3) * r * .15) * vw, (bird.y + Math.sin(-d * .2 - 1.3) * r * .15) * vh); ctx.fill(); // Draw enemy circle(ctx, '#FF80D4', enemy.x * vw, enemy.y * vh, r * vh); // Eyes circle(ctx, '#ffffff', (enemy.x - 2) * vw, enemy.y * vh, (r - 3.5) * vh); circle(ctx, '#ffffff', (enemy.x + 2) * vw, enemy.y * vh, (r - 3.5) * vh); circle(ctx, '#000000', (enemy.x - 2) * vw, (enemy.y - .25) * vh, .25 * vh); circle(ctx, '#000000', (enemy.x + 2) * vw, (enemy.y - .25) * vh, .25 * vh); //Snout circle(ctx, '#FFCCEE', enemy.x * vw, (enemy.y + 1) * vh, (r - 3) * vh); circle(ctx, '#330022', (enemy.x - .75) * vw, (enemy.y + .5) * vh, .65 * vh); circle(ctx, '#330022', (enemy.x + .75) * vw, (enemy.y + .75) * vh, .45 * vh); // Draw predictions var pred = nextPos({x: bird.x, y: bird.y, dx: bird.dx, dy: bird.dy}); if (state == 'sling') for (var i = 0; i <= 5; ++i) { pred = nextPos(nextPos(pred)); circle(ctx, '#47b4eb', pred.x * vw, pred.y * vh, 1 * vh); } // Game info ctx.fillStyle = '#0d5173'; cText(ctx, 'FowlPlay', 50 * vw, 15 * vh); if (state == 'sling') cText(ctx, 'Force: ' + f, 50 * vw, 25 * vh); if (state == 'won') cText(ctx, 'Congrats, you win!', 50 * vw, 25 * vh); // Move bird and enemy if (state == 'launched') bird = nextPos(bird); enemy = nextPos(enemy); // Calculate new direction if (state == 'sling') { bird.dx = Math.cos(-a * .2 - .8) * f; bird.dy = Math.sin(-a * .2 - .8) * f; } // Check if bird and enemy overlap if ((bird.x - enemy.x) * (bird.x - enemy.x) + (bird.y - enemy.y) * (bird.y - enemy.y) < 4 * r * r) state = 'won'; // Next attempt if bird is still for 30 frames or game is reset if ((Math.abs(bird.dx) < 0.1 && Math.abs(bird.dy) < 0.1) || state == 'won') { ++s; } else { s = 0; } if ((s > 30 && state != 'sling')|| state == 'reset') { // Reset game state, bird position and get random location for enemy from current time state = 'sling'; bird = {x: 20, y: 61.5, dx: 0, dy: 0}; $.get('/Dev/Time/LocalTime', function(v){ enemy.x = parseInt(formatValue(v, 'time s')) % 11 * 4 + 40; }) } } // Helper function to center text function cText(ctx, text, x, y) { ctx.fillText(text, x - ctx.measureText(text).width / 2, y); } // Helper function to draw a circle function circle(ctx, color, x, y, rd) { ctx.fillStyle = color; ctx.beginPath(); ctx.arc(x, y, rd, 0, 6.3); ctx.closePath(); ctx.fill(); } // Helper method to calculate next position function nextPos(obj) { obj.dy += g; obj.x += obj.dx; obj.y += obj.dy; // Snap to ground if below ground surface after movement for simplicity if (obj.y >= 80 - r) { obj.y = 80 - r; obj.dy *= -.9; obj.dx *= .9; } return obj; } // Helper function to handle push button input function handleGameIO(v) { // Up and down button-click changes the launch angle if (v == 10 && state == 'sling') ++a; if (v == 20 && state == 'sling') --a; // Up-button-longpress changes the force of the slingshot if (v == 11 && state == 'sling') f = (f % 4) + 1; // Down-button-longpress launches the bird if (v == 21 && state == 'sling') state = 'launched'; if (v == 20 && state == 'launched') state = 'reset'; } " onActivate = "$.subscribe('/Dev/Time/Tick10hz', function(){ control('#cnv', 'REFRESH'); })" > <div id="suuntoplus"> <uiViewSet id="view"> <div id="welcome"> <div style="width: 100%; height: 50%; top: 0%; left: 0%;"><img src="btn-shape-top.png" class="c-yellow"></div> <div class="f-b-s cm-fgc" style="top: 13%; left: calc(79% - 100%e);" >CHANGE FORCE</div> <div style="width: 50px; height: 50px; top: calc(30% - 50%e); left: calc(90% - 50%e);"><img class="cm-bgc" src="hint-btn-top.png" /></div> <div class="p-m"> <span class="f-m">FowlPlay</span><br/> Click buttons to aim<br /> Longpress for action<br /> Launch away! </div> <div style="width: 100%; height: 50%; top: calc(100% - 100%e); left: 0%;"><img src="btn-shape-btm.png" class="c-yellow p-b"></div> <div class="f-b-s cm-fgc" style="top: calc(87% - 100%e); left: calc(79% - 100%e);" >LAUNCH BIRD</div> <div style="width: 50px; height: 50px; top: calc(70% - 50%e); left: calc(90% - 50%e);"><img class="cm-bgc" src="hint-btn-bottom.png" /></div> <userInput> <pushButton name="down" onLongPress="navigate('#view', 'game')"> </userInput> </div> <div id="game"> <object id="cnv" type="canvas" build="ctx => renderGame(ctx)" style="width: 100%; height: 100%; top: 0%; left: 0%;"/> <userInput> <pushButton name="up" onClick="handleGameIO(10)" onLongPressStart="handleGameIO(11)" /> <pushButton name="down" onClick="handleGameIO(20)" onLongPressStart="handleGameIO(21)" /> </userInput> </div> </uiViewSet> </div> </uiView> If you have the SuuntoPlusEditor-plugin installed in Visual Studio Code and your Suunto watch connected to your laptop you are now ready to test the app: Create an empty folder and open it in Visual Studio Code Create new files manifest.json, main.js and g.html Copy-paste the code snippets above to their respective files Navigate to the bottom of the Explorer view where these files are listed. Under the SuuntoPlus Apps section hover the mouse over the newly created app and click on the watch icon (Deploy to Watch). Wait for the building and uploading to finish; the app is now available on your watch under SuuntoPlus once you start a new exercise. Please note that the app does not work in the simulator. In this example, the g.html file handles everything from layout and user interactions (watch buttons) to game logic and rendering. Let’s remove the game-related code and focus on the layout and user interactions first. <uiView> <div id="suuntoplus"> <uiViewSet id="view"> <div id="welcome"> <div style="width: 100%; height: 50%; top: 0%; left: 0%;"><img src="btn-shape-top.png" class="c-yellow"></div> <div class="f-b-s cm-fgc" style="top: 13%; left: calc(79% - 100%e);" >CHANGE FORCE</div> <div style="width: 50px; height: 50px; top: calc(30% - 50%e); left: calc(90% - 50%e);"><img class="cm-bgc" src="hint-btn-top.png" /></div> <div class="p-m"> <span class="f-m">FowlPlay</span><br/> Click buttons to aim<br /> Longpress for action<br /> Launch away! </div> <div style="width: 100%; height: 50%; top: calc(100% - 100%e); left: 0%;"><img src="btn-shape-btm.png" class="c-yellow p-b"></div> <div class="f-b-s cm-fgc" style="top: calc(87% - 100%e); left: calc(79% - 100%e);" >LAUNCH BIRD</div> <div style="width: 50px; height: 50px; top: calc(70% - 50%e); left: calc(90% - 50%e);"><img class="cm-bgc" src="hint-btn-bottom.png" /></div> <userInput> <pushButton name="down" onLongPress="navigate('#view', 'game')"> </userInput> </div> <div id="game"> <!-- The game logic will be added here --> </div> </uiViewSet> </div> </uiView> Every sport app template has a <uiView> element representing the Suunto watch as their root element. It is good practice to wrap the content inside <uiView> in a <div> (you can think of these as the <html> and <body> elements of HTML). This app has two different screens: a welcome screen and the actual game. This can be achieved with a <uiViewSet> element containing two <div> elements. All three elements have an id attribute for easy referencing in the code. This can be used for example to change which of the <div> elements is displayed. This is done with: <userInput> <pushButton name="down" onLongPress="navigate('#view', 'game')"> </userInput> The <userInput> element can contain several <pushButton> elements. A <pushButton> has a name attribute to identify the button (here set to “down” for the bottom-right button) and event listeners for different types of user actions (here we listen to a longpress with onLongPress). Because the <userInput> element is defined inside the welcome screen’s <div> element, the interactions are captured only when the welcome screen is visible. navigate is a global built-in function of the watch that takes two parameters here: a query string to identify the targeted <uiViewSet> element and the id attribute of the <div> element to display. The rest of the <div> elements are used to set the position and dimensions (setting CSS styles (top/left/width/height) with the style attribute; % represents a percentage of the parent element’s width/height while %e represents a percentage of the current element’s width/height) as well as the font and color (through class names with the class attribute) of texts and images on the welcome screen. The class names and images used here are part of the watch and can be used without explicit definition or importing. The first letter of the class name typically defines its purpose: c is for colors (fg = foreground; bg = background; cm = current (light/dark) mode), f is for font styles and p is for positioning. The game logic is defined in an event listener attached to the <uiView>. Here we use the onLoad attribute (notice the uppercase L compared to the HTML standard) that behaves similarly to the onLoad function that could be defined in main.js except the symbols (functions and variables) defined here are in scope for (and thus can be used in) the entire HTML template. We take advantage of this for rendering. First we define a drawing surface in our layout using: <object id="cnv" type="canvas" build="ctx => renderGame(ctx)" style="width: 100%; height: 100%; top: 0%; left: 0%;"/> An <object> element with a type attribute of “canvas” works similarly to a HTML5 <canvas>. The content will be rendered using the anonymous function defined by the build attribute. Here we capture the argument (that is a modified version of a CanvasRenderingContext2D from the JavaScript Canvas API) and pass it onto the function renderGame. By setting the id and style attributes we ensure the canvas can be easily referenced later and takes up the entire screen respectively. This setup draws our game just once, but for a smooth gaming experience the screen needs to be updated continuously. This is achieved by adding the following attribute to the <uiView> element: onActivate = "$.subscribe('/Dev/Time/Tick10hz', function(){ control('#cnv', 'REFRESH'); })" Here the $ object allows us to access the devices resources. Here we use the subscribe method to attach a change listener to a resource. The '/Dev/Time/Tick10hz' resource will trigger the callback function approximately 10 times per second (this essentially works like setInterval in JavaScript). control is a global built-in function of the watch that takes two parameters: a query string to identify the target element and the control signal to send it. Here this causes the <canvas> to be refreshed (=rerendered). The actual rendering is handled by the renderGame function defined in the onLoad attribute of the <uiView>: function renderGame(ctx) { // Viewport width and height from canvas width and height var vw = ctx.width / 100; var vh = ctx.height / 100; // Draw sky ctx.fillStyle = '#CCEEFF'; ctx.fillRect(0, 0, 100 * vw, 100 * vh); // Draw ground ctx.fillStyle = '#14B814'; ctx.fillRect(0, 80 * vh, 100 * vw, 20 * vh); // Draw slingshot ctx.strokeStyle = '#8A380F'; ctx.fillStyle = '#8A380F'; ctx.beginPath(); ctx.lineWidth = 3 * vh; ctx.lineCap = 'round' ctx.arc(20 * vw, 61.5 * vh, 7.5 * vh, 0, 3.15); ctx.stroke(); ctx.fillRect(18 * vw, 70 * vh, 4 * vw, 10 * vh); // Draw bird circle(ctx, '#F2DF0D', bird.x * vw, bird.y * vh, r * vh); // Calculate current direction d = (state == 'sling') ? a : (!!bird.dx ? (Math.atan(bird.dy / bird.dx) + .8) / -.2 : 0); // Draw beak ctx.fillStyle = '#F97706'; ctx.beginPath(); ctx.moveTo((bird.x + Math.cos(-d * .2 - 1 ) * r * .45) * vw, (bird.y + Math.sin(-d * .2 - 1 ) * r * .45) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - .4) * r * .95) * vw, (bird.y + Math.sin(-d * .2 - .4) * r * .95) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 + .2) * r * .45) * vw, (bird.y + Math.sin(-d * .2 + .2) * r * .45) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - .4) * r * .2 ) * vw, (bird.y + Math.sin(-d * .2 - .4) * r * .2 ) * vh); ctx.closePath(); ctx.fill(); // Draw eyes ctx.fillStyle = '#000000'; ctx.beginPath(); ctx.moveTo((bird.x + Math.cos(-d * .2 - 2.1) * r * .25) * vw, (bird.y + Math.sin(-d * .2 - 2.1) * r * .25) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.4) * r * .5 ) * vw, (bird.y + Math.sin(-d * .2 - 1.4) * r * .5 ) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.5) * r * .75) * vw, (bird.y + Math.sin(-d * .2 - 1.5) * r * .75) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.2) * r * .75) * vw, (bird.y + Math.sin(-d * .2 - 1.2) * r * .75) * vh); ctx.lineTo((bird.x + Math.cos(-d * .2 - 1.3) * r * .15) * vw, (bird.y + Math.sin(-d * .2 - 1.3) * r * .15) * vh); ctx.fill(); // Draw enemy circle(ctx, '#FF80D4', enemy.x * vw, enemy.y * vh, r * vh); // Eyes circle(ctx, '#ffffff', (enemy.x - 2) * vw, enemy.y * vh, (r - 3.5) * vh); circle(ctx, '#ffffff', (enemy.x + 2) * vw, enemy.y * vh, (r - 3.5) * vh); circle(ctx, '#000000', (enemy.x - 2) * vw, (enemy.y - .25) * vh, .25 * vh); circle(ctx, '#000000', (enemy.x + 2) * vw, (enemy.y - .25) * vh, .25 * vh); //Snout circle(ctx, '#FFCCEE', enemy.x * vw, (enemy.y + 1) * vh, (r - 3) * vh); circle(ctx, '#330022', (enemy.x - .75) * vw, (enemy.y + .5) * vh, .65 * vh); circle(ctx, '#330022', (enemy.x + .75) * vw, (enemy.y + .75) * vh, .45 * vh); // Draw predictions var pred = nextPos({x: bird.x, y: bird.y, dx: bird.dx, dy: bird.dy}); if (state == 'sling') for (var i = 0; i <= 5; ++i) { pred = nextPos(nextPos(pred)); circle(ctx, '#47b4eb', pred.x * vw, pred.y * vh, 1 * vh); } // Game info ctx.fillStyle = '#0d5173'; cText(ctx, 'FowlPlay', 50 * vw, 15 * vh); if (state == 'sling') cText(ctx, 'Force: ' + f, 50 * vw, 25 * vh); if (state == 'won') cText(ctx, 'Congrats, you win!', 50 * vw, 25 * vh); // Move bird and enemy if (state == 'launched') bird = nextPos(bird); enemy = nextPos(enemy); // Calculate new direction if (state == 'sling') { bird.dx = Math.cos(-a * .2 - .8) * f; bird.dy = Math.sin(-a * .2 - .8) * f; } // Check if bird and enemy overlap if ((bird.x - enemy.x) * (bird.x - enemy.x) + (bird.y - enemy.y) * (bird.y - enemy.y) < 4 * r * r) state = 'won'; // Next attempt if bird is still for 30 frames or game is reset if ((Math.abs(bird.dx) < 0.1 && Math.abs(bird.dy) < 0.1) || state == 'won') { ++s; } else { s = 0; } if ((s > 30 && state != 'sling')|| state == 'reset') { // Reset game state, bird position and get random location for enemy from current time state = 'sling'; bird = {x: 20, y: 61.5, dx: 0, dy: 0}; $.get('/Dev/Time/LocalTime', function(v){ enemy.x = parseInt(formatValue(v, 'time s')) % 11 * 4 + 40; }) } } The drawing is achieved by using methods familiar from the JavaScript Canvas API such as fillRect, beginPath, arc, moveTo, lineTo, fillText, measureText, stroke and fill as well as reading and modifying properties such as width, height, fillStyle, strokeStyle, lineWidth and lineCap. It is notable that the drawing context can be passed on to other function as is done in this example with the following helper functions:: // Helper function to center text function cText(ctx, text, x, y) { ctx.fillText(text, x - ctx.measureText(text).width / 2, y); } // Helper function to draw a circle function circle(ctx, color, x, y, rd) { ctx.fillStyle = color; ctx.beginPath(); ctx.arc(x, y, rd, 0, 6.3); ctx.closePath(); ctx.fill(); } We have also defined some constants and variables to manage the game’s physics and state (and once again defined a helper function to move the bird): // Set constants for world gravity (g) and bird/enemy radius(r) var g = .2, r = 5; // Set the current state (one of 'sling', 'launched', 'won' or 'reset') and initialize sleep timer (s) var state = 'sling', s = 0; // Set variables for the launch force (f), launch angle (a) and current direction (d; angle) var f = 2, a = 0, d = 0; // Define the position and velocities for the bird and enemy var bird = {x: 20, y: 61.5, dx: 0, dy: 0}; var enemy = {x: 80, y: 80 - r, dx: 0, dy: 0}; // Helper method to calculate next position function nextPos(obj) { obj.dy += g; obj.x += obj.dx; obj.y += obj.dy; // Snap to ground if below ground surface after movement for simplicity if (obj.y >= 80 - r) { obj.y = 80 - r; obj.dy *= -.9; obj.dx *= .9; } return obj; } Here a game object (the bird or pig) is defined by its x and y coordinates as well as its horizontal and vertical velocity (dx and dy). One quirk of the code is how the new pseudo-random location for the pig is calculated. $.get('/Dev/Time/LocalTime', function(v){ enemy.x = parseInt(formatValue(v, 'time s')) % 11 * 4 + 40; }) Here we fetch (the get method from the $ object works similarly to subscribe method except this is only executed once) the current time, treat the value as a time and format it to only include the current second (using the built-in function formatValue). Then we convert the seconds to a number and clamp it to fit our game world. The final step to complete the game is to attach event listeners to user input. This is similar to before except this time we use a helper function to handle the mapping of the users actions to correct changes to the game depending on the game’s state. <userInput> <pushButton name="up" onClick="handleGameIO(10)" onLongPressStart="handleGameIO(11)" /> <pushButton name="down" onClick="handleGameIO(20)" onLongPressStart="handleGameIO(21)" /> </userInput> // Helper function to handle push button input function handleGameIO(v) { // Up and down button-click changes the launch angle if (v == 10 && state == 'sling') ++a; if (v == 20 && state == 'sling') --a; // Up-button-longpress changes the force of the slingshot if (v == 11 && state == 'sling') f = (f % 4) + 1; // Down-button-longpress launches the bird if (v == 21 && state == 'sling') state = 'launched'; if (v == 20 && state == 'launched') state = 'reset'; } The app combines many different capabilities of Suunto watches. Feel free to ask any questions you might have about this example app. Hopefully it inspired you to create some awesome. Happy launching; may your idle moments forever be fowl‑filled!
    • P

      Erg mode for the Wahoo Kickr Core

      Watching Ignoring Scheduled Pinned Locked Moved SuuntoPlus™ Sports Apps
      2
      1 Votes
      2 Posts
      104 Views
      E
      @peringmar Its a more general issue, i have a Suito-T elite trainer and I also can’t control it, it would be nice to tackle this for all trainers