diff --git a/_config.yml b/_config.yml new file mode 100644 index 000000000..c74188174 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-slate \ No newline at end of file diff --git a/apps.json b/apps.json index 7ec25b67f..d5e301251 100644 --- a/apps.json +++ b/apps.json @@ -171,7 +171,7 @@ { "id": "setting", "name": "Settings", "icon": "settings.png", - "version":"0.22", + "version":"0.23", "description": "A menu for setting up Bangle.js", "tags": "tool,system", "readme": "README.md", @@ -2621,6 +2621,25 @@ {"name":"lifeclk.img","url":"app-icon.js","evaluate":true} ] }, +{ "id": "speedalt", + "name": "GPS Speedo and Altimeter", + "shortName":"GPS Speed Alt", + "icon": "app.png", + "version":"0.07", + "description": "GPS speed and altitude display. Designed for easy viewing and use during outdoor activities such as para-gliding, hang-gliding, sailing, cycling etc.", + "tags": "tool,outdoors", + "type":"app", + "allow_emulator":true, + "readme": "README.md", + "storage": [ + {"name":"speedalt.app.js","url":"app.js"}, + {"name":"speedalt.img","url":"app-icon.js","evaluate":true}, + {"name":"speedalt.settings.js","url":"settings.js"} + ], + "data": [ + {"name":"speedalt.json"} + ] +}, { "id": "de-stress", "name": "De-Stress", "shortName":"De-Stress", diff --git a/apps/setting/ChangeLog b/apps/setting/ChangeLog index 229337dc9..bb47dfe4a 100644 --- a/apps/setting/ChangeLog +++ b/apps/setting/ChangeLog @@ -25,3 +25,4 @@ 0.21: Add passkey pairing option (BETA) Add whitelist option (fix #78) 0.22: Move HID to BLE menu +0.23: Change max time offset to 13 for NZ summer daylight time (NZDT) diff --git a/apps/setting/settings.js b/apps/setting/settings.js index 6e766afd4..8e839bdb1 100644 --- a/apps/setting/settings.js +++ b/apps/setting/settings.js @@ -327,7 +327,7 @@ function showLocaleMenu() { 'Time Zone': { value: settings.timezone, min: -11, - max: 12, + max: 13, step: 0.5, onchange: v => { settings.timezone = v || 0; diff --git a/apps/speedalt/ChangeLog b/apps/speedalt/ChangeLog new file mode 100644 index 000000000..258b5051a --- /dev/null +++ b/apps/speedalt/ChangeLog @@ -0,0 +1,7 @@ +0.01 : Initial import. +0.02 : Misc development. +0.03 : Enable screen off. +0.04 : Vibrate once on no fix, twice on fix. +0.05 : Add setting to turn vibrate on/off. +0.06 : Tweaks to vibration settings. +0.07 : Switch to BTN1 for Max toggle and reset function. diff --git a/apps/speedalt/README.md b/apps/speedalt/README.md new file mode 100644 index 000000000..e1d00653e --- /dev/null +++ b/apps/speedalt/README.md @@ -0,0 +1,18 @@ +Displays the GPS speed and altitude. One is displayed on the watch face using the largest possible characters depending on the number of digits. The other is in a smaller characters below that. Both are always visible. You can display the current or maximum observed speed/altitude values. Current time is always displayed. + +Display Tap : Swaps the displays. You can have either speed or altitude on the large primary display. + +BTN1 : Short press < 2 secs toggles the displays between showing the current speed/alt values or the maximum values recorded. + +BTN1 : Long press > 2 secs resets the recorded maximum values. + +App Settings : Select the desired display units. Speed can be as per the default locale, kph, knots, mph or m/s. Altitude can be feet or metres. Select one of three colour schemes. Colours, high contrast (all white on black) or night ( all red on black ). Vibration can be used to indicate when a fix is lost or gained. One buzz for a lost fix and a double buzz when a fix is found. + +![](screen1.png) +![](screen2.png) +![](screen3.png) +![](screen4.png) + +Developed for my use in sailing, cycling and motorcycling. If you find this software useful or have feedback drop me a line mike[at]kereru.com. Enjoy! + +( Many thanks to Gordon Williams. Awesome job. ) diff --git a/apps/speedalt/app-icon.js b/apps/speedalt/app-icon.js new file mode 100644 index 000000000..6c03df55b --- /dev/null +++ b/apps/speedalt/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("m02xH+AH4AJlgAMGWQ4lEw1Wq2BAAgHBG8QhFq+C1nXABGswQ4GGjhkBGJIAHOQI2ZDIcyGaQ3EmQ3WNAiaKABusNyoUDNCxuGGySdDNDBuGUoY0e2etAAWzGzoOCGheJxNlAA2J1o2LGprTNGRAAFbZw0LqwZI1pkGAAgJDUxdWGxTULFAmJFJGzURjaET6Q0DGZKYIrqjRT5Q0EGZwpEUZZqPaYaRMGg6LINhBJKNK40JAAI1Iq6fJGjDrIUQoVCwRqJDhA0OXYQRGwRsEAgWsNTCeIDYTwG1g1GqygJNROsq0ymVWq7TI2ZRJUQg1JUARkMAAg0FDhY1GDAxOKQwgAEKI6IKKAQ1KeAQ1IJ4VXAog1gC4Q1ImQVBAwYFBmQ1K1o1fMoQ1ORIQ1Ua5ahD1uzrqnEULo1L1gVBAAusGrDxHC4NlEY4aDAAZQGDhiHCGpZOJNodWmUyqxpIRBY1GQw7wCURAAPKIWtXhI1EwSFJNhIAMNQSgHwQ1EGwVXKBJsWDJSgENgjyKGyg0CNQ/XNQo1DwIRGbIS+HABYWLwI1GGwVWKhZtQChigGNhg2TCRhqIGwcy1g2YCBmsmQ0INgajIYgY1PdRKfCGpCjMG4ShNBxSfKGwyjIADOsGho1DbRI0YagQ1MG0Y0RGwjbLACLTDGh42FqxuY1lWGig2FmRuWwKdDGiZuHG6WBNC42JHAWCVBWswQyEGjQ3IHAJyBAAgHBCAwzbG5QAMGb44SGUgAkA")) diff --git a/apps/speedalt/app.js b/apps/speedalt/app.js new file mode 100644 index 000000000..7ccb20935 --- /dev/null +++ b/apps/speedalt/app.js @@ -0,0 +1,408 @@ +/* +Speed and Altitude [speedalt] +Ver : 0.07 +Mike Bennett mike[at]kereru.com +*/ + +const dbg = 0; + +var buf = Graphics.createArrayBuffer(240,160,2,{msb:true}); + +// Load fonts +require("Font7x11Numeric7Seg").add(Graphics); + +/* +var mainmenu = { + "" : { "title" : "-- Units --" }, + "default" : function() { setUnits(0,''); }, + "Kph (spd)" : function() { setUnits(1,'kph'); }, + "Knots (spd)" : function() { setUnits(1.852,'knots'); }, + "Mph (spd)" : function() { setUnits(1.60934,'mph'); }, + "m/s (spd)" : function() { setUnits(3.6,'m/s'); }, + "Meters (alt)" : function() { setUnitsAlt(1,'m'); }, + "Feet (alt)" : function() { setUnitsAlt(0.3048,'feet'); }, + "Exit" : function() { exitMenu(); }, // remove the menu and restore +}; +*/ + +var lastFix = {fix:0,satellites:0}; +var showSpeed = 1; // 1 = Speed in primary display. 0 = alt in primary +var showMax = 0; // 1 = display the max values. 0 = display the cur fix +var maxPress = 0; // Time max button pressed. Used to calculate short or long press. +var canDraw = 1; +var lastBuzz = 0; // What sort of buzz was last performed. 0 = no fix, 1 = fix. +var timerBuzz2 = 0; // ID of timer for fix second buzz +var time = ''; // Last time string displayed. Re displayed in background colour to remove before drawing new time. + +var max = {}; +max.spd = 0; +max.alt = 0; + +var emulator = 0; +if (process.env.BOARD=="EMSCRIPTEN") emulator = 1; // 1 = running in emulator. Supplies test values; + +function drawFix(speed,units,sats,alt,alt_units) { + if (!canDraw) return; + + buf.clear(); + + var val = ''; + var u=''; + + // Primary Display + val = speed.toString(); + if ( !showSpeed ) val = alt.toString(); + + // Primary Units + u = settings.spd_unit; + if ( !showSpeed ) u = alt_units; + + drawPrimary(val,u); + + // Secondary Display + val = alt.toString(); + if ( !showSpeed ) val = speed.toString(); + + // Secondary Units + u = alt_units; + if ( !showSpeed ) u = settings.spd_unit; + + drawSecondary(val,u); + + // Time + drawTime(); + + //Sats + drawSats(sats); + + g.reset(); + g.drawImage(img,0,40); +// g.flip(); + + +} + + +function drawNoFix(sats) { + if (!canDraw) return; + var u; + + buf.clear(); + + buf.setFontAlign(0,0); + buf.setColor(3); + + buf.setFontVector(25); + buf.drawString("Waiting for GPS",120,56); + + // Time + drawTime(); + + //Sats + drawSats(sats); + + g.reset(); + g.drawImage(img,0,40); +// g.flip(); + + +} + +function drawPrimary(n,u) { + + // Primary Display + + var s=40; // Font size + if ( n.length <= 7 ) s=48; + if ( n.length <= 6 ) s=55; + if ( n.length <= 5 ) s=68; + if ( n.length <= 4 ) s=85; + if ( n.length <= 3 ) s=110; + + buf.setFontAlign(0,-1); //Centre + buf.setColor(1); + + buf.setFontVector(s); + buf.drawString(n,110,0); + + // Primary Units + buf.setFontAlign(1,-1,3); //right + buf.setColor(2); + buf.setFontVector(25); + buf.drawString(u,210,0); +} + +function drawSecondary(n,u) { + + var s=180; // units X position + if ( n.length <= 5 ) s=155; + if ( n.length <= 4 ) s=125; + if ( n.length <= 3 ) s=100; + if ( n.length <= 2 ) s=65; + if ( n.length <= 1 ) s=35; + + buf.setFontAlign(-1,1); //left, bottom + buf.setColor(1); + buf.setFontVector(45); + buf.drawString(n,5,140); + + // Secondary Units + buf.setFontAlign(-1,1); //left, bottom + buf.setColor(2); + buf.setFontVector(25); + buf.drawString(u,s,135); +} + + +function drawTime() { + var x = 0; + var y = 160; + + buf.setFont("7x11Numeric7Seg", 2); + buf.setFontAlign(-1,1); //left, bottom + + buf.setColor(0); + buf.drawString(time,x,y); + time = require("locale").time(new Date(),1); + buf.setColor(3); + buf.drawString(time,x,y); +} + +function drawSats(sats) { + buf.setFontAlign(1,1); //right, bottom + buf.setColor(3); + buf.setFont("6x8", 2); + if ( showMax ) { + buf.setColor(2); + buf.drawString("MAX",240,160); + } + else buf.drawString("Sats:"+sats,240,160); +} + +function onGPS(fix) { + lastFix = fix; + + var m; + + if (fix.fix || emulator) { + doBuzz(1); + + //==== Speed ==== + if ( settings.spd == 0 ) { + var strSpeed = require("locale").speed(fix.speed); + m = strSpeed.match(/([0-9,\.]+)(.*)/); // regex splits numbers from units + + if ( emulator ) { + speed = '125'; //testing only + settings.spd_unit = 'kph'; + } + else { + speed = m[1]; + settings.spd_unit = m[2]; + } + } + // Calculate for selected units + else { + speed = fix.speed; + if ( emulator ) speed = '100'; + speed = Math.round(parseFloat(speed)/parseFloat(settings.spd),0); + } + + // ==== Altitude ==== + alt = fix.alt; + if ( emulator ) alt = '360'; + alt = Math.round(parseFloat(alt)/parseFloat(settings.alt),0); + + // Record max values + if (parseFloat(speed) > parseFloat(max.spd) ) max.spd = parseFloat(speed); + if (parseFloat(alt) > parseFloat(max.alt) ) max.alt = parseFloat(alt); + + if ( showMax ) drawFix(max.spd,settings.spd_unit,fix.satellites,max.alt,settings.alt_unit); + else drawFix(speed,settings.spd_unit,fix.satellites,alt,settings.alt_unit); + + } else { + doBuzz(0); + drawNoFix(fix.satellites); + } + +} + +// Vibrate watch when fix lost or gained. +function doBuzz(hasFix) { + + // nothing to do + if ( lastBuzz === hasFix || !settings.buzz ) { + return; + } + + // fix gained - double buzz + if ( !lastBuzz && hasFix ) { + if ( dbg ) print('Fix'); + lastBuzz = 1; + Bangle.buzz(); + timerBuzz2 = setInterval(doBuzz2, 600); // Trigger a second buzz + return; + } + + // fix lost - single buzz + if ( lastBuzz && !hasFix ) { + if ( dbg ) print('Fix lost'); + lastBuzz = 0; + Bangle.buzz(); + return; + } + + +} + +// Second buzz +function doBuzz2() { + if ( dbg ) print('Buzz2'); + clearInterval(timerBuzz2); + Bangle.buzz(); + } + +function toggleDisplay() { + showSpeed = !showSpeed; + onGPS(lastFix); // Back to Speed display +} + +function toggleMax() { +// if ( inMenu ) return; + showMax = !showMax; + onGPS(lastFix); // Back to Speed display +} + +function setButtons(){ + + // Show launcher when middle button pressed + setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"}); + + // Switch between fix and max display on short press or reset max values on long press + setWatch(maxPressed, BTN1,{repeat:true,edge:"rising"}); + setWatch(maxReleased, BTN1,{repeat:true,edge:"falling"}); + + // Touch screen to toggle display + setWatch(toggleDisplay, BTN4, {repeat:true,edge:"falling"}); + setWatch(toggleDisplay, BTN5, {repeat:true,edge:"falling"}); + + +} + +function maxPressed() { + maxPress = getTime(); +} + +function maxReleased() { + var dur = getTime()-maxPress; + + if ( dur < 2 ) toggleMax(); // Short press toggle fix/max display + else { + max.spd = 0; // Long press resets max values. + max.alt = 0; + onGPS(lastFix); // redraw display + } +} + +function updateClock() { + if ( dbg ) print('Updating clock'); + if (!canDraw) return; + + drawTime(); + g.reset(); + g.drawImage(img,0,40); +// g.flip(); + + // Something different to display in the emulator + if ( emulator ) { + max.spd++; + max.alt++; + } + +} + +function startDraw(){ + canDraw=true; + g.clear(); + Bangle.drawWidgets(); + onGPS(lastFix); // draw app screen +} + +function stopDraw() { + canDraw=false; +} + +// ===== Main Prog ===== + +// Read settings. +let settings = require('Storage').readJSON('speedalt.json',1)||{}; + +settings.spd = settings.spd||0; // Multiplier for speed unit conversions. 0 = use the locale values for speed +settings.spd_unit = settings.spd_unit||''; // Displayed speed unit +settings.alt = settings.alt||0.3048;// Multiplier for altitude unit conversions. +settings.alt_unit = settings.alt_unit||'feet'; // Displayed altitude units +settings.colour = settings.colour||0; // Colour scheme. +settings.buzz = settings.buzz||0; // Buzz when fix lost or gained. + +/* +Colour Pallet Idx +0 : Background (black) +1 : Speed/Alt +2 : Units +3 : Sats +*/ +var img = { + width:buf.getWidth(), + height:buf.getHeight(), + bpp:2, + buffer:buf.buffer, + palette:new Uint16Array([0,0x4FE0,0xEFE0,0x07DB]) +}; + +if ( settings.colour == 1 ) img.palette = new Uint16Array([0,0xFFFF,0xFFFF,0xFFFF]); +if ( settings.colour == 2 ) img.palette = new Uint16Array([0,0xFF800,0xF800,0xF800]); + + +// Find speed unit if using locale speed +if ( settings.spd == 0 ) { + var strSpeed = require("locale").speed(1); + m = strSpeed.match(/([0-9,\.]+)(.*)/); // regex splits numbers from units + settings.spd_unit = m[2]; +} + +var SCREENACCESS = { + withApp:true, + request:function(){ + this.withApp=false; + stopDraw(); + clearWatch(); + }, + release:function(){ + this.withApp=true; + startDraw(); + setButtons(); + } +}; + +Bangle.on('lcdPower',function(on) { + if (!SCREENACCESS.withApp) return; + if (on) { + startDraw(); + } else { + stopDraw(); + } +}); + +// All set up. Lets go. +g.clear(); +Bangle.setLCDBrightness(1); +Bangle.loadWidgets(); +Bangle.drawWidgets(); +Bangle.setGPSPower(1); + +onGPS(lastFix); +Bangle.on('GPS', onGPS); + +setButtons(); +setInterval(updateClock, 30000); + diff --git a/apps/speedalt/app.png b/apps/speedalt/app.png new file mode 100644 index 000000000..41849d307 Binary files /dev/null and b/apps/speedalt/app.png differ diff --git a/apps/speedalt/screen1.png b/apps/speedalt/screen1.png new file mode 100644 index 000000000..eac5e7e7a Binary files /dev/null and b/apps/speedalt/screen1.png differ diff --git a/apps/speedalt/screen2.png b/apps/speedalt/screen2.png new file mode 100644 index 000000000..37d7bb7cf Binary files /dev/null and b/apps/speedalt/screen2.png differ diff --git a/apps/speedalt/screen3.png b/apps/speedalt/screen3.png new file mode 100644 index 000000000..9e061958e Binary files /dev/null and b/apps/speedalt/screen3.png differ diff --git a/apps/speedalt/screen4.png b/apps/speedalt/screen4.png new file mode 100644 index 000000000..1082d59b4 Binary files /dev/null and b/apps/speedalt/screen4.png differ diff --git a/apps/speedalt/settings.js b/apps/speedalt/settings.js new file mode 100644 index 000000000..21171edee --- /dev/null +++ b/apps/speedalt/settings.js @@ -0,0 +1,61 @@ +(function(back) { + + let settings = require('Storage').readJSON('speedalt.json',1)||{}; + //settings.buzz = settings.buzz||1; + + function writeSettings() { + require('Storage').write('speedalt.json',settings); + } + + function setUnits(m,u) { + settings.spd = m; + settings.spd_unit = u; + writeSettings(); + } + + function setUnitsAlt(m,u) { + settings.alt = m; + settings.alt_unit = u; + writeSettings(); + } + + function setColour(c) { + settings.colour = c; + writeSettings(); + } + + const appMenu = { + '': {'title': 'GPS Speed Alt'}, + '< Back': back, + 'Units' : function() { E.showMenu(unitsMenu); }, + 'Colours' : function() { E.showMenu(colMenu); }, + 'Vibrate' : { + value : settings.buzz, + format : v => v?"On":"Off", + onchange : () => { settings.buzz = !settings.buzz; writeSettings(); } + }}; + + const unitsMenu = { + '': {'title': 'Units'}, + '< Back': function() { E.showMenu(appMenu); }, + 'default (spd)' : function() { setUnits(0,''); }, + 'Kph (spd)' : function() { setUnits(1,'kph'); }, + 'Knots (spd)' : function() { setUnits(1.852,'knots'); }, + 'Mph (spd)' : function() { setUnits(1.60934,'mph'); }, + 'm/s (spd)' : function() { setUnits(3.6,'m/s'); }, + 'Meters (alt)' : function() { setUnitsAlt(1,'m'); }, + 'Feet (alt)' : function() { setUnitsAlt(0.3048,'feet'); } + }; + + const colMenu = { + '': {'title': 'Colours'}, + '< Back': function() { E.showMenu(appMenu); }, + 'Default' : function() { setColour(0); }, + 'Hi Contrast' : function() { setColour(1); }, + 'Night' : function() { setColour(2); } + }; + + + E.showMenu(appMenu); + +})