diff --git a/apps/bthrm/ChangeLog b/apps/bthrm/ChangeLog index 5560f00bc..27a58dd78 100644 --- a/apps/bthrm/ChangeLog +++ b/apps/bthrm/ChangeLog @@ -1 +1,4 @@ 0.01: New App! +0.02: Make overriding the HRM event optional + Emit BTHRM event for external sensor + Add recorder app plugin diff --git a/apps/bthrm/boot.js b/apps/bthrm/boot.js index 88e574480..0aa8d5c96 100644 --- a/apps/bthrm/boot.js +++ b/apps/bthrm/boot.js @@ -2,24 +2,43 @@ var log = function() {};//print var gatt; var status; - - Bangle.isHRMOn = function() { + + var origIsHRMOn = Bangle.isHRMOn; + + Bangle.isBTHRMOn = function(){ return (status=="searching" || status=="connecting") || (gatt!==undefined); } - Bangle.setHRMPower = function(isOn, app) { + + Bangle.isHRMOn = function() { + var settings = require('Storage').readJSON("bthrm.json", true) || {}; + + print(settings); + if (settings.enabled && !settings.replace){ + return origIsHRMOn(); + } else if (settings.enabled && settings.replace){ + return Bangle.isBTHRMOn(); + } + return origIsHRMOn() || Bangle.isBTHRMOn(); + } + + Bangle.setBTHRMPower = function(isOn, app) { + + + var settings = require('Storage').readJSON("bthrm.json", true) || {}; + // Do app power handling if (!app) app="?"; - log("setHRMPower ->", isOn, app); + log("setBTHRMPower ->", isOn, app); if (Bangle._PWR===undefined) Bangle._PWR={}; - if (Bangle._PWR.HRM===undefined) Bangle._PWR.HRM=[]; - if (isOn && !Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM.push(app); - if (!isOn && Bangle._PWR.HRM.includes(app)) Bangle._PWR.HRM = Bangle._PWR.HRM.filter(a=>a!=app); - isOn = Bangle._PWR.HRM.length; + if (Bangle._PWR.BTHRM===undefined) Bangle._PWR.BTHRM=[]; + if (isOn && !Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM.push(app); + if (!isOn && Bangle._PWR.BTHRM.includes(app)) Bangle._PWR.BTHRM = Bangle._PWR.BTHRM.filter(a=>a!=app); + isOn = Bangle._PWR.BTHRM.length; // so now we know if we're really on if (isOn) { - log("setHRMPower on", app); - if (!Bangle.isHRMOn()) { - log("HRM not already on"); + log("setBTHRMPower on", app); + if (!Bangle.isBTHRMOn()) { + log("BTHRM not already on"); status = "searching"; NRF.requestDevice({ filters: [{ services: ['180D'] }] }).then(function(device) { log("Found device "+device.id); @@ -49,7 +68,11 @@ if (flags&16) { var interval = dv.getUint16(idx,1); // in milliseconds }*/ - Bangle.emit('HRM',{ + + + var eventName = settings.replace ? "HRM" : "BTHRM"; + + Bangle.emit(eventName, { bpm:bpm, confidence:100 }); @@ -65,15 +88,27 @@ }); } } else { // not on - log("setHRMPower off", app); + log("setBTHRMPower off", app); if (gatt) { - log("HRM connected - disconnecting"); + log("BTHRM connected - disconnecting"); status = undefined; try {gatt.disconnect();}catch(e) { - log("HRM disconnect error", e); + log("BTHRM disconnect error", e); } gatt = undefined; } } }; + + var origSetHRMPower = Bangle.setHRMPower; + + Bangle.setHRMPower = function(isOn, app) { + var settings = require('Storage').readJSON("bthrm.json", true) || {}; + if (settings.enabled || !isOn){ + Bangle.setBTHRMPower(isOn, app); + } + if (settings.enabled && !settings.replace || !isOn){ + origSetHRMPower(isOn, app); + } + } })(); diff --git a/apps/bthrm/bthrm.js b/apps/bthrm/bthrm.js new file mode 100644 index 000000000..7c80c735f --- /dev/null +++ b/apps/bthrm/bthrm.js @@ -0,0 +1,61 @@ +var btm = g.getHeight()-1; +var eventInt = null; +var eventBt = null; +var counterInt = 0; +var counterBt = 0; + + +function draw(y, event, type, counter) { + var px = g.getWidth()/2; + g.reset(); + g.setFontAlign(0,0); + g.clearRect(0,y,g.getWidth(),y+80); + if (type == null || event == null || counter == 0) return; + var str = event.bpm + ""; + g.setFontVector(40).drawString(str,px,y+20); + str = "Confidence: " + event.confidence; + g.setFontVector(12).drawString(str,px,y+50); + str = "Event: " + type; + g.setFontVector(12).drawString(str,px,y+60); +} + +function onBtHrm(e) { + print("Event for BT " + JSON.stringify(e)); + counterBt += 5; + eventBt = e; +} + +function onHrm(e) { + print("Event for Int " + JSON.stringify(e)); + counterInt += 5; + eventInt = e; +} + +Bangle.on('BTHRM', onBtHrm); +Bangle.on('HRM', onHrm); + +Bangle.setHRMPower(1,'bthrm') +Bangle.setBTHRMPower(1,'bthrm') + +g.clear(); +Bangle.loadWidgets(); +Bangle.drawWidgets(); + +g.reset().setFont("6x8",2).setFontAlign(0,0); +g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2 - 16); + +function drawInt(){ + counterInt--; + if (counterInt < 0) counterInt = 0; + if (counterInt > 5) counterInt = 5; + draw(24, eventInt, "HRM", counterInt); +} +function drawBt(){ + counterBt--; + if (counterBt < 0) counterBt = 0; + if (counterBt > 5) counterBt = 5; + draw(100, eventBt, "BTHRM", counterBt); +} + +var interval = setInterval(drawInt, 1000); +var interval = setInterval(drawBt, 1000); diff --git a/apps/bthrm/metadata.json b/apps/bthrm/metadata.json index dd36ff408..22cb26663 100644 --- a/apps/bthrm/metadata.json +++ b/apps/bthrm/metadata.json @@ -2,15 +2,18 @@ "id": "bthrm", "name": "Bluetooth Heart Rate Monitor", "shortName": "BT HRM", - "version": "0.01", + "version": "0.02", "description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.", "icon": "app.png", - "type": "boot", + "type": "app", "tags": "health,bluetooth", "supports": ["BANGLEJS","BANGLEJS2"], "readme": "README.md", "storage": [ + {"name":"bthrm.app.js","url":"bthrm.js"}, + {"name":"bthrm.recorder.js","url":"recorder.js"}, {"name":"bthrm.boot.js","url":"boot.js"}, - {"name":"bthrm.img","url":"app-icon.js","evaluate":true} + {"name":"bthrm.img","url":"app-icon.js","evaluate":true}, + {"name":"bthrm.settings.js","url":"settings.js"} ] } diff --git a/apps/bthrm/recorder.js b/apps/bthrm/recorder.js new file mode 100644 index 000000000..40f64a676 --- /dev/null +++ b/apps/bthrm/recorder.js @@ -0,0 +1,27 @@ +(function(recorders) { + recorders.bthrm = function() { + var bpm = 0; + function onHRM(h) { + bpm = h.bpm; + } + return { + name : "BTHR", + fields : ["BT Heartrate"], + getValues : () => { + result = [bpm]; + bpm = 0; + return result; + }, + start : () => { + Bangle.on('BTHRM', onHRM); + Bangle.setBTHRMPower(1,"recorder"); + }, + stop : () => { + Bangle.removeListener('BTHRM', onHRM); + Bangle.setBTHRMPower(0,"recorder"); + }, + draw : (x,y) => g.setColor(Bangle.isBTHRMOn()?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y) + }; + } +}) + diff --git a/apps/bthrm/settings.js b/apps/bthrm/settings.js new file mode 100644 index 000000000..8cb00614e --- /dev/null +++ b/apps/bthrm/settings.js @@ -0,0 +1,33 @@ +(function(back) { + var FILE = "bthrm.json"; + + var settings = Object.assign({ + enabled: true, + replace: true, + }, require('Storage').readJSON(FILE, true) || {}); + + function writeSettings() { + require('Storage').writeJSON(FILE, settings); + } + + E.showMenu({ + '': { 'title': 'Bluetooth HRM' }, + '< Back': back, + 'Use BT HRM': { + value: !!settings.enabled, + format: v => settings.enabled ? "On" : "Off", + onchange: v => { + settings.enabled = v; + writeSettings(); + } + }, + 'Use HRM event': { + value: !!settings.replace, + format: v => settings.replace ? "On" : "Off", + onchange: v => { + settings.replace = v; + writeSettings(); + } + } + }); +}) diff --git a/apps/pooqround/ChangeLog b/apps/pooqround/ChangeLog index 8eb91cf97..bc35b69df 100644 --- a/apps/pooqround/ChangeLog +++ b/apps/pooqround/ChangeLog @@ -1,2 +1,3 @@ 0.00: Initial check-in. 0.01: Add tap-to-decorate feature. Bugfixes. +0.02: Add autorotate while charging. Remove autolight. Tweak fonts. Add some haptic feedback on touchscreen operations. diff --git a/apps/pooqround/README.md b/apps/pooqround/README.md index d413fd88e..6f6bafa69 100644 --- a/apps/pooqround/README.md +++ b/apps/pooqround/README.md @@ -20,8 +20,7 @@ you can quickly alter the number of ‘hands’ on the display. When the watch i or down to remove the distraction. There's also a setting that displays the second hand, but only if the watch is perfectly face-to-the-sky, in case you want the ability to check the _exact_ time, hands free, without the impact on battery life this usually entails. -In some versions of the Bangle.js firmware, the backlight doesn't come on automatically when you twist your wrist. There's currently a workaround -for this integrated into the watchface; you can disable it in the menu, if you prefer. +While charging the main display automatically rotates so that noon is up. This can be disabled. ## Limitations diff --git a/apps/pooqround/app.js b/apps/pooqround/app.js index 22cf5ff79..06e208e61 100644 --- a/apps/pooqround/app.js +++ b/apps/pooqround/app.js @@ -25,8 +25,8 @@ // // This only works for Bangle 2. -const isString = x => typeof x === 'string'; -const imageWidth = i => isString(i) ? i.charCodeAt(0) : i.width; +const isString = x => typeof x === 'string', + imageWidth = i => isString(i) ? i.charCodeAt(0) : i.width; ////////////////////////////////////////////////////////////////////////////// /* System integration */ @@ -115,9 +115,9 @@ class RoundOptions extends Options { onchange: x => this.calendric = x, format: x => ['none', 'day', 'date', 'both', 'month', 'full'][x], }, - 'Auto-Illum.': { - init: _ => this.autolight, - onchange: x => this.autolight = x + 'Autorotate': { + init: _ => this.autorotate, + onchange: x => this.autorotate = x }, Defaults: _ => {this.reset(); this.interact();} }); @@ -133,7 +133,7 @@ RoundOptions.defaults = { calendric: 5, dayFg: '#fff', nightFg: '#000', - autolight: true, + autorotate: true, }; ////////////////////////////////////////////////////////////////////////////// @@ -144,29 +144,29 @@ const dec = x => E.toString(heatshrink.decompress(atob(x))); const y10F = [ dec( 'g///EAh////AA4IIBgPwgE+gAOBg/AngXB+EPAYM8gfggEfgF8D4OAj4dB8EDAYI' + - 'fBBAISBAAMOAYUB4AECnEAkAuBgEQBAPgIYX8IYX/wYDCEwIiMMgUYgECCIZlBAY' + - 'N4CoRUBIoMP8AZBge8CgMB8+BCAPw+F/gf8jxDB/0D4BGBEQMPAYIeBoAfBnEwge' + - 'Ah0cB4MDx4PBgHn4EB8E7LQM8h/eJ4MDBgIpB+H+g/wnE/WwMMO4P8LwM/XAJLBT' + - 'gY7BAAN/wC9CQwV+jwDB/4pBgP/EQKYBBIIxBPQP+SATfCIYIiCO4I9BBwM//hlB' + - 'PQJlCwYGBTAPgIgM4CYM8hwKBMoODegPA8F+gZlBewP4hz/BE4QrBGgM/LAV//4+' + - 'BAYJyBPwM/KQMeGQMPFwM8H4UHBIPwGQNwn4yBnhxBGQJxBGQK5BGQKWDOwUACAM' + - 'D/BDCNYPg///8E5HwR2BIwMDSgK0FSocMAYTLBAAYpBQAPnDwJGBEwK+B/hlB+F8' + - 'TARABTAJABTAPBMoR+BMoKXBDoX5DwIuBMoUPS4THCGwJbBhAaBvh5B+EHwPAOwP' + - 'guA1BvCcB4E8nxlBn1/VoIyBwDKBO4SGCgA=' + 'fBBAISBAAMOAYUB4AECnEAkAuBgEQBAPgIYX8IYX/wYDCEwIiMMgUcgECCIZlBAY' + + 'N4CoRUBIoMP8AZBge8MoMB8+B8B4B+E/gf4jw/B/kD4ADBEQMPSYXgoAfBnEwgeA' + + 'hw7BvEDx4PBgHn4EB8E7LQM8h/eJ4MDBgIpB+H+g/wnE/WwMMG4ReBn4zBJYKcDH' + + '4IABv+AXoSGCv0eAYP/FIMB/4iBTAIJBGIJ6B/yQCb4RDBEQTlBHoIOBn51BwC+B' + + 'MoWDAwKYBRgKYBCYM8hwKBMoODegPA8F+gZlBewP4hz/BE4QrBGgM/LAV//4+BAY' + + 'JyBPwM/KQMeGQMPFwM8H4UHBIPwGQNwZgPwnhxBGQJxBGQK5BGQKWDOwUACALlBI' + + 'YRrB8H///gnI+COwJGBgaUBWgqVDhgDCZYIADFIKAB84eBIwImBXwP8MoPwviYCI' + + 'AKYBIAKYB4JlCPwJlBS4IdC/IeBFwJlCh6XCY4Q2BLYMIDQN8PIPwg+B4B2B8FwG' + + 'oN4TgPAnk+MoM+v6tBGQOAZQJ3CQwUAA' ), 48, dec('hgAI'), 34 ];const y1F = [ dec( - 'g//AAPggE/AoX8gF/AoX+gF8CoU+gHwAoUPgAZBEIQFGCIodFFIo1FIIoADnAFEj' + - 'gFEh0AhA1EiAFCgeAFIf/4A1DFQIED/5MDGB6OEjAECHIIYDhkAuAFCjwFEj6DEn' + - '+AAod74AFD/PgvAtC+Hwv/wgZSBvEfLwc8RISOBGAJsBVAXgggEBE4PgIgJLC8E8' + - 'I4fgXQS/B8IhBGwOA8YFCgfA9+eAoMB4H/j/ACIPA/kPCQJCB/DMDMoMBboYVBKo' + - 'IDBSYeAAoYlCAATpEg/4Xwc/QIcPFoJcBQIP8GILXCDYLXBbId//BeCL4QwDgIwD' + - 'AAIXBDAQfCEYSPBAoaPCPQKPCAoZgBAoYvBAoIXBBAIFB/ALDEoJHBAoaPDaQSPB' + - 'AoKcBJgY9DTQX/EoKmCC4SyCYYJJB+CHBj+Aj8ASYJNBBINwIIOAM4ILDAYN/wAB' + - 'BB4JBBI45vCRYgADApEHL4pHB8AECFIPhAYLCCAggFBAgaNCYwgFEbAkAwAFEc4S' + - 'PCj/+LIKPBv6PEAoRnBFIMDFYLXCKoTLDa4YRDBYIdDh4FDMoQ1DK4ZBBMQIDBJY' + - 'bWBFIMEIIQpBgxxBgZRBh8AAYN8AoQVBjgbBAoTZBvwRCvEBF4IdB+E/OIp9CJgZ' + - 'BCQQUAA=' + 'g//AAPggE/AoX8gF/AoX+gF8CoU+gHwAoUPgAZBEIQFGCIodFFIo1FIIoADnEAgQ' + + 'FCjkAgwFCh0Ahg1EBoIABgeAFIf/4A1DFQIED/5MDGAYADEQYwDRwgMDhAYEH4Nw' + + 'AoUeAok/QYl/wAFD/fAHgUD+PgvAFBj/g+E/4EBLAN4j5SCgE8h4EB/AwCAoOAVA' + + 'PgggeBFoPgQgRLB8E8I4fgXQS/B8KwBMgOA8YFCgfA9+eAoMB4H/j/ACIPA/kPCQ' + + 'JIB/DMDMoJSBboQVBKoIDBSYZOBAAQlCAATpEg/4Xwc/QIZyBwBcBgf//gxBa4Qb' + + 'Ba4LZDv/4LwRfCGAcBGAYABC4IYCD4QjCR4IFDR4R6BR4QFDMAIFDF4IFBC4IIBA' + + 'oLEBBYQlBI4IFDR4ZrBR4QFBTgJMDHoaaCdQSmCC4SyCYYJJB+CHBj+Aj8ASYJNB' + + 'BINwIIOAM4ILDAYN/wABBB4JBBI45vCRYgADApEHL4pHB8AECFIPhAYLCCAggFBA' + + 'gaNCYwgFEbAkAwAFEc4SPCj/+LIKPBv6PEAoRnBFIMDFYLXCKoTLDa4YRDBYIdDh' + + '4FDMoQ1DK4ZBBMQIDBJYbWBFIMEIIQpBgxxBgZRBh8AAYN8AoQVBjgbBAoTZBvwR' + + 'CvEBF4IdB+E/OIp9CJgZBCQQUAA=' ), 48, dec('hgAI'), 48 ];const y10sF = [ dec( @@ -194,20 +194,20 @@ const y10F = [ ];const d1F = [ dec( 'AB1/+AECj///4FCAgP/8EAgf/4F//EAg4CBgf8gEPwAUBn0AhwaCAYMeAoUPgEcA' + - 'oUHAowRFDoopFGopBFJopZGBgIKCABlAIIcA4AFDgIFEgZBCAoMHAohVBAoY6CHg' + - 'U/Aol/AogADGoQFUABEMAQM/AQN8bIRZBRgJ5BLILhBgP3LIcD84rDg/HWYcPw4F' + - 'Dj4PBAoU+Aol8Aon4PocB+CJDgfgAoXgh/ATYX4v+AU4X//w/DbYQFCCwJ3PvDIE' + - 'NYQCCdoJ6CgfAiCGCI4NwgEeFwISCLoMeJwJdCnkfHYd4v4FD+f5AoUB9/BAoUD/' + - '4jCh8HG4IpCh5DBAIMeE4Q/BvjMCfoP8Z4Uf//wCgInB/5lCABs+AoicBAAUDAok' + - 'P9wFDv+OCAjUCHQP4AoY5BAoUHEIIFCv5JBAoLQBLQYqEApQpDArIAJv5IBnBTCV' + - '4McJAQFBcYLvBB4IkBd4N4cYQBBeoLdBCYIFDngFECoIFDOwIdCc4QpCFwIZCjwu' + - 'BEoU8FwIxCvAIBEIPB+AUBJIP/8AmBLYWAd4RnBdx4XCcYf/Dgn//AuEP4LjBXoJ' + - 'AC//vQYT0BBIKDC+CZBOIM/wAFDVYIFCgIrBAoUDPoIdCO4QnBaQYnBGoQVBIIZI' + - 'CJoTNCLIY4CAYIaDAAKRCAASRDAAIaEYAQtDYAI5DRgZFCAAYuCQoQuBAgIFBvEH' + - 'AgIFB+CgBAAMB86lE76EBFwX/GocPNoYmBIwk/HQl8LpIAQRId/SoYDB4ZJCUoPn' + - 'VoUHwP3Y4YYBY4k+Y4h5BdILhBd4YFFCIodFFIo1FIIpNFLIplGAArMFn6oBHYMA' + - 'DYQFBgP5E4IFBgfgUgIFCwBZBEAL1BPYZbDA4Z7DLYRtCBYYlDBoIxCEYMBHoIvC' + - 'HAI7Dh5PBI4X/LIX//7+Dn52Eh4QCA==' + 'oUHAowRFDoopFGopBFJopZGBgIKCAB5BBgA1CAoMBAokDCIgTCAYRTDAoI6CHgU/' + + 'Aol/Aog1GAqgAIhgCBn4CBvjZCLIKMBPIJZBcIMB+4lBMoMD84rDg/HL4cPw4FDj' + + '5rEnwFEvgFE/AFBaYMB+CJCwED8AFC8EP4CbC/F/wCnC//+H4bbCAoQWBO594EAI' + + 'TBgBrCAQTtBPQUD4EQQwRHBuEAjwuBCQRdBjxOBLoU8j47DvF/Aofz/IFCgPv4IF' + + 'Cgf/EYUPg43BFIUPIYIBBjwnCH4N8ZgT9B/jPCj//+AUBE4P/MoQANnwFETgIACg' + + 'YFEh/uAod/xwQEagQ6B/AFDHIIFCg4hBAoV/JIIFBaAJaDFQgFKFIYFZABN/JAM4' + + 'KYSvBjhICAoLjBd4IPBEgLvBvDjCAIL1BboITBAoc8AogVBAoZ2BDoTnCFIQuBDI' + + 'UeFwIlCnguBGIV4BAIhB4PwCgJJB//gEwJbCwDvCM4LuPC4TjD/4cE//4Fwh/BcY' + + 'K9BIAX/96DCegIJBQYXwTIJxBn+AAoarBAoUBFYIFCgZ9BDoR3CE4LSDE4I1CCoJ' + + 'BDJARNCZoRZDHAQDBDQYABSIQACSIYABDQjACFobABHIaMDIoQADFwSFCFwIEBAo' + + 'N4g4EBAoPwUAIABgPnUonfQgIuC/41Dh5tDEwJGEn46EvhdJACCJDv6VDAYPDJIS' + + 'lB86tCg+B+7HDDALHEnzHEPILpBcILvDAooRFDoopFGopBFJopZFMowAFZgs/VAI' + + '7BgAbCAoMB/InBAoMD8CkBAoWALIIgBeoJ7DLYYHDPYZbCNoQLDEoYNBGIQjBgI9' + + 'BF4Q4BHYcPJ4JHC/5ZC///fwc/OwkPCAQA==' ), 48, dec('ikPigAGA'), 48 ];const dowF = [ dec( @@ -220,10 +220,10 @@ const y10F = [ 'kDMIgeBFIQEBBYRTBCAZ3FAggAMg4zEj7LEn7LEv++AodzxwFD+ePAofjw4FVDoo' + 'pFv+eIImcJomYLImAAoZeEAtTyBAAQFEVYIFDSQIvhAojaCFwgABh4YEngFEuAqJ' + 'gPAAocDApYuEgP/fgl/+B9HAAv+Aon8HQMOIAkeAokcAohaDAoM4Aol4AohmDAoJ' + - 'BDAoJsDAo7vhABbJDAo9/AojEFMYbKMArCBDFI41FWIYABggFEgbuCDYMPLIQbBj' + - '//wBdCn0H4DZCvEBb4YZBdYZBBAofgCIQFDDoIFFDoPggYFBF4IFBGoI7B+AFCE4' + - 'NwCIIlCuAdBIYU4gPwn5VBjC7B/y0Dv/4YwcPCwMAjJlCAAM584FDufDCAUA8eBA' + - 'p/zC4n5EYj1BAoc//4RDU4IFDA==' + 'BDAoJsDAo7vhABZuBQYoFDv4FEYgpjDZRgFYGYYpHGoqxDAAMEAokDdwQbBh//DY' + + 'cf/+ALoU+g/AbIV4gLfDDILrDIIIFD8ARCAoYdBAoodB8EDAoIvBAoI1BHYPwAoQ' + + 'nBuARBEoVwDoJDCnEB+E/KoMYXYP+Wgd//DGDh4WBgEZMoQABnPnAodz4YQCgHjw' + + 'IFP+YXE/IjEeoIFDn//CIanBAoY=' ), 48, dec('kElkMljsljw='), 48 ];const mF = [ dec( @@ -322,21 +322,20 @@ class Round { this.r = this.xc - this.minR; } - reset(clear) {this.state = {}; clear && this.g.clear(true);} + reset(clear) {this.state = {}; clear == null || this.g.clear(true).setRotation(clear);} doIcons(which) { this.state[which] = null; - this.render(new Date()); // Not quite right, I think. } enhanceUntil(t) {this.enhance = t;} pie(f, a0, a1, invert) { if (!invert) return this.pie(f, a1, a0 + 1, true); - let t0 = Math.tan(a0 * 2 * Math.PI), t1 = Math.tan(a1 * 2 * Math.PI); + const t0 = Math.tan(a0 * 2 * Math.PI), t1 = Math.tan(a1 * 2 * Math.PI); let i0 = Math.floor(a0 * 4 + 0.5), i1 = Math.floor(a1 * 4 + 0.5); - let x = f.getWidth() / 2, y = f.getHeight() / 2; - let poly = [ + const x = f.getWidth() / 2, y = f.getHeight() / 2; + const poly = [ x + (i1 & 2 ? -x : x) * (i1 & 1 ? 1 : t1), y + (i1 & 2 ? y : -y) / (i1 & 1 ? t1 : 1), x, @@ -348,16 +347,17 @@ class Round { for (i0++; i0 <= i1; i0++) poly.push( 3 * i0 & 2 ? f.getWidth() : 0, i0 & 2 ? f.getHeight() : 0 ); - f.setColor(0).fillPoly(poly); + return f.setColor(0).fillPoly(poly); } hand(t, d, c0, r0, c1, r1) { + const g = this.g; t *= Math.PI / 30; - const r = this.r; - const z = 2 * r0 + 1; - const x = this.xc + r * Math.sin(t), y = this.yc - r * Math.cos(t); - const x0 = x - r0, y0 = y - r0; - d = d ? d[0] : Graphics.createArrayBuffer(z, z, 16, {msb: true}); + const r = this.r, + z = 2 * r0 + 1, + x = this.xc + r * Math.sin(t), y = this.yc - r * Math.cos(t), + x0 = x - r0, y0 = y - r0; + d = d ? d[0] : Graphics.createArrayBuffer(z, z, 4, {msb: true}); for (let i = 0; i < z; i++) for (let j = 0; j < z; j++) { d.setPixel(i, j, g.getPixel(x0 + i, y0 + j)); } @@ -366,24 +366,20 @@ class Round { return [d, x0, y0]; } - render(d) { - const g = this.g; - const b = this.b, bI = this.bI; - const c = this.c, cI = this.cI; - const e = d < this.enhance; - const state = this.state; - const options = this.options; - const cal = options.calendric; - const res = options.resolution; - const dow = (e || cal == 1 || cal > 2) && d.getDay(); - const ts = res < 2 && d.getSeconds(); - const tm = (e || res < 3) && d.getMinutes() + ts / 60; - const th = d.getHours() + d.getMinutes() / 60; - const dd = (e || cal > 1) && d.getDate(); - const dm = (e || cal > 3) && d.getMonth(); - const dy = (e || cal > 4) && d.getFullYear(); - const xc = this.xc, yc = this.yc, r = this.r; - const dlr = xc * 3/4, dlw = 8, dlhw = 4; + render(d, rate) { + const g = this.g, b = this.b, bI = this.bI, c = this.c, cI = this.cI, + e = d < this.enhance, + state = this.state, options = this.options, + cal = options.calendric, res = options.resolution, + dow = (e || cal === 1 || cal > 2) && d.getDay(), + ts = res < 2 && d.getSeconds(), + tm = (e || res < 3) && d.getMinutes() + ts / 60, + th = d.getHours() + d.getMinutes() / 60, + dd = (e || cal > 1) && d.getDate(), + dm = (e || cal > 3) && d.getMonth(), + dy = (e || cal > 4) && d.getFullYear(); + const xc = this.xc, yc = this.yc, r = this.r, + dlr = xc * 3/4, dlw = 8, dlhw = 4; // Restore saveunders for fast-moving, overdrawing indicators. if (state.sd) g.drawImage.apply(g, state.sd); @@ -397,10 +393,10 @@ class Round { state.dow = dow; } - const locked = Bangle.isLocked(); - const charging = Bangle.isCharging(); - const battery = E.getBattery(); - const HRMOn = Bangle.isHRMOn(); + const locked = Bangle.isLocked(), + charging = Bangle.isCharging(), + battery = E.getBattery(), + HRMOn = Bangle.isHRMOn(); if (dy !== state.dy || locked !== state.locked || charging !== state.charging || @@ -463,6 +459,7 @@ class Round { this.hand(tm, state.md, g.theme.bg, this.minR, g.theme.fg, this.minR - 1) : null; state.sd = ts === +ts ? + rate > 1000 ? this.hand(ts, state.sd, g.theme.fg2, this.secR, g.theme.bg, 2) : this.hand(ts, state.sd, g.theme.fg2, this.secR) : null; } @@ -482,13 +479,23 @@ class Clock { this.listeners = { lcdPower: on => on ? this.active() : this.inactive(), - charging: () => {face.doIcons('charging'); this.active();}, + charging: on => { + face.doIcons('charging'); + if (on) { + this.listeners.accel = + a => this.orientation(a) === this.attitude || this.active(); + Bangle.on('accel', this.listeners.accel); + } else { + Bangle.removeListener('accel', this.listeners.accel); + delete this.listeners.accel; + } + this.active(); + }, lock: () => {face.doIcons('locked'); this.active();}, faceUp: up => { this.conservative = !up; this.active(); }, - twist: _ => this.options.autolight && Bangle.setLCDPower(true), drag: e => { if (this.t0) { if (e.b) { @@ -498,20 +505,23 @@ class Clock { if (e.y - this.e0.y < -50) { this.options.resolution > 0 && this.options.resolution--; this.rates.clock = this.timescales[this.options.resolution]; + this.ack(); this.active(); } else if (e.y - this.e0.y > 50) { this.options.resolution < this.timescales.length - 1 && this.options.resolution++; this.rates.clock = this.timescales[this.options.resolution]; + this.ack(); this.active(); } else if (this.yX - this.yN < 20) { const now = new Date(); if (now - this.t0 < 250) { + this.ack(); face.enhanceUntil(now + 30000); - face.render(now); + this.active(); } else if (now - this.t0 > 500) { this.stop(); - this.options.interact(); + this.ack().then(_ => this.options.interact()); } } this.t0 = null; @@ -524,9 +534,25 @@ class Clock { }; } + ack() { + return Bangle.buzz(33); + } + + orientation(a) { + return Math.abs(a.z) < 0.85 ? + Math.abs(a.y) > Math.abs(a.x) ? a.y < 0 ? 0 : 2 : a.x > 0 ? 1 : 3 : + 0; + } + + rotation() { + return this.options.autorotate && Bangle.isCharging() ? + this.orientation(Bangle.getAccel()) : + 0; + } + redraw(rate) { const now = this.updated = new Date(); - if (this.refresh) this.face.reset(true); + if (this.refresh) this.face.reset(this.attitude = this.rotation()); this.refresh = false; rate = this.face.render(now, rate); if (rate !== this.rates.face) { @@ -541,13 +567,13 @@ class Clock { this.exception && clearTimeout(this.exception); this.interval && clearInterval(this.interval); this.timeout = this.exception = this.interval = this.rate = null; - this.face.reset(false); // Cancel any ongoing background rendering + this.face.reset(); // Cancel any ongoing background rendering return this; } active() { - const prev = this.rate; - const now = Date.now(); + const prev = this.rate, + now = Date.now(); let rate = Infinity; for (const k in this.rates) { let r = this.rates[k]; diff --git a/apps/pooqround/metadata.json b/apps/pooqround/metadata.json index 934d35a5b..27f98a7b6 100644 --- a/apps/pooqround/metadata.json +++ b/apps/pooqround/metadata.json @@ -1,7 +1,7 @@ { "id": "pooqround", "name": "pooq Round watch face", "shortName":"pooq Round", - "version":"0.01", + "version":"0.02", "description": "A 24 hour analogue watchface with high legibility and a novel style.", "icon": "app.png", "type": "clock", diff --git a/apps/pooqround/resourcer.js b/apps/pooqround/resourcer.js index 44186e658..17c35a40d 100644 --- a/apps/pooqround/resourcer.js +++ b/apps/pooqround/resourcer.js @@ -1,6 +1,6 @@ // pooqRoman resource maker // -// Copyright (c) 2021 Stephen P Spackman +// Copyright (c) 2021, 2022 Stephen P Spackman // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -147,18 +147,18 @@ res += prepFont('y10', ` xxx xxx -2-------------------------------- - x xx + x xxx xx xxx xxxx xxx xxxxx xxx xxxxxxx xxx xxxx xxx xxx - xxxx xxxx xxx - xxxx xxxx xxx - xxxx xxxxxxxx xxxxxxx - xxxx xxxxxxxxxxxxxxxxxxx + xxxx xxxx xxxx + xxxx xxxxx xxxx + xxxx xxxxxxx xxxxxx + xxxx xxxxxxxxxxxxxxxxx xxxx xxxxxxxxxxxxxx -xxxx xxxxxxxxxx +xxxx xxxxxxxxx -3-------------------------------- xxx x xxx xxx xx xxx @@ -270,10 +270,10 @@ res += prepFont('y1', ` xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx -1---------------------------------------------- xxx - xxx - xxx - xxx x - xxx x + xxx x + xxx xx + xxx xx + xxx xxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx @@ -282,18 +282,18 @@ res += prepFont('y1', ` xxx xxx -2---------------------------------------------- - x xx + x xxx xx xxx xxxx xxx - xxxxx xxx - xxxxxxx xxx - xxxx xxxx xxx - xxxx xxxxx xxx - xxxx xxxxxxx xxxx - xxxx xxxxxxxxxxxxx xxxxxxxxxxx - xxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - xxxx xxxxxxxxxxxxxxxxxxxxxxxxx -xxxx xxxxxxxxxxxxxx + xxxxxx xxx + xxxxxxxx xxx + xxxx xxxxx xxx + xxxx xxxxxx xxxx + xxxx xxxxxxxx xxxx + xxxx xxxxxxxxxxx xxxxxxxx + xxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxx + xxxx xxxxxxxxxxxxxxxxxxxxxxxxx +xxxx xxxxxxxxxxxxxxxxx -3---------------------------------------------- xxx x xxx xxx xx xxx @@ -645,12 +645,12 @@ xxxx xxxx -1---------------------------------------------- -xxx x -xxx xx -xxx xxx -xxx xxx -xxx xxxx -xxx xxxx +xxx +xxx x +xxx xx +xxx xx +xxx xxx +xxx xxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx @@ -993,9 +993,9 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxx - xxxxxxxxxxxxxxxx - xxxxxxxxxxxxxxxx - xxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxxxxx xxxx xxxx xxx diff --git a/apps/ptlaunch/README.md b/apps/ptlaunch/README.md index cf75315a9..12c205980 100644 --- a/apps/ptlaunch/README.md +++ b/apps/ptlaunch/README.md @@ -2,11 +2,19 @@ Directly launch apps from the clock screen with custom patterns. -## Usage +## Installation and Usage + +Install Pattern Launcher alongside your main laucher app. +_Do not delete that launcher!_ +Pattern Launcher is designed as an additional app launching utility, not as a replacement for the main launcher. + +In the main launcher, start Pattern Launcher in the app menu to assign the pattern configuration (see below). +Note that this actually among the applications, _not_ in the application settings! Create patterns and link them to apps in the Pattern Launcher app. Then launch the linked apps directly from the clock screen by simply drawing the desired pattern. +Note that this does only work in the clock screen, not if other applications run. ## Add Pattern Screenshots @@ -28,7 +36,8 @@ Then launch the linked apps directly from the clock screen by simply drawing the ## Detailed Steps -From the main menu you can: +The main menu of Pattern Launcher is accessible from the _application_ starter of the main launcher. +From there you can: - Add a new pattern and link it to an app (first entry) - To create a new pattern first select "Add Pattern" @@ -60,6 +69,16 @@ Make sure the watch is unlocked before you start drawing. If this bothers you, y Please note that drawing on the clock screen will not visually show the pattern you drew. It will start the app as soon as the pattern was recognized - this might take 1 or 2 seconds! If still nothing happens, that might be a bug, sorry! +4. Where can I configure the patterns? + +You have to start the "Pattern Launcher" app from the main app launcher's app selection. + +5. Do I have to delete my former app launcher so that Pattern Launcher is the only installed launcher? + +No! Pattern Launcher works alongside another "main" launcher. +If you have deleted that one, you do not have a general purpose app launcher any more and cannot access Pattern Launcher's configuration. +If you already have deleted your main launcher accidentially, just reinstall it from the app loader. + ## Authors Initial creation: [crazysaem](https://github.com/crazysaem) @@ -67,3 +86,5 @@ Initial creation: [crazysaem](https://github.com/crazysaem) Improve pattern detection code readability: [PaddeK](http://forum.espruino.com/profiles/117930/) Improve pattern rendering: [HughB](http://forum.espruino.com/profiles/167235/) + +Doc additions: [dirkhillbrecht](http://forum.espruino.com/profiles/182498/) diff --git a/apps/recorder/ChangeLog b/apps/recorder/ChangeLog index bedc63141..dbf086f7d 100644 --- a/apps/recorder/ChangeLog +++ b/apps/recorder/ChangeLog @@ -4,3 +4,7 @@ 0.03: Fix theme and maps/graphing if no GPS 0.04: Multiple bugfixes 0.05: Add recording for coresensor +0.06: Add recording for battery stats + Fix execution of other recorders (*.recorder.js) + Modified icons and colors for better visibility + Only show plotting speed if Latitude is available diff --git a/apps/recorder/README.md b/apps/recorder/README.md index 4a4561f1c..87be34424 100644 --- a/apps/recorder/README.md +++ b/apps/recorder/README.md @@ -16,7 +16,8 @@ You can record * **Time** The current time * **GPS** GPS Latitude, Longitude and Altitude * **Steps** Steps counted by the step counter -* **HR** Heart rate +* **HR** Heart rate and confidence +* **BAT** Battery percentage and voltage * **Core** CoreTemp body temperature **Note:** It is possible for other apps to record information using this app @@ -25,4 +26,4 @@ function in `widget.js` for more information. ## Tips -When recording GPS, it usually takes several minutes for the watch to get a [GPS fix](https://en.wikipedia.org/wiki/Time_to_first_fix). There is a grey satellite symbol, which you will see turn red when you get an actual GPS Fix. You can [upload assistant files](https://banglejs.com/apps/#assisted%20gps%20update) to speed up the time spent on getting a GPS fix. +When recording GPS, it usually takes several minutes for the watch to get a [GPS fix](https://en.wikipedia.org/wiki/Time_to_first_fix). There is a red satellite symbol, which you will see turn green when you get an actual GPS Fix. You can [upload assistant files](https://banglejs.com/apps/#assisted%20gps%20update) to speed up the time spent on getting a GPS fix. diff --git a/apps/recorder/app.js b/apps/recorder/app.js index fcd8d6031..5b1c63aef 100644 --- a/apps/recorder/app.js +++ b/apps/recorder/app.js @@ -199,9 +199,10 @@ function viewTrack(filename, info) { menu['Plot Alt.'] = function() { plotGraph(info, "Altitude"); }; - menu['Plot Speed'] = function() { - plotGraph(info, "Speed"); - }; + if (info.fields.includes("Latitude")) + menu['Plot Speed'] = function() { + plotGraph(info, "Speed"); + }; // TODO: steps, heart rate? menu['Erase'] = function() { E.showPrompt("Delete Track?").then(function(v) { diff --git a/apps/recorder/metadata.json b/apps/recorder/metadata.json index fc840278b..0f3b25ad2 100644 --- a/apps/recorder/metadata.json +++ b/apps/recorder/metadata.json @@ -2,7 +2,7 @@ "id": "recorder", "name": "Recorder (BETA)", "shortName": "Recorder", - "version": "0.05", + "version": "0.06", "description": "Record GPS position, heart rate and more in the background, then download to your PC.", "icon": "app.png", "tags": "tool,outdoors,gps,widget", diff --git a/apps/recorder/widget.js b/apps/recorder/widget.js index 4d8cdddb1..8f82f1f37 100644 --- a/apps/recorder/widget.js +++ b/apps/recorder/widget.js @@ -48,41 +48,50 @@ Bangle.removeListener('GPS', onGPS); Bangle.setGPSPower(0,"recorder"); }, - draw : (x,y) => g.setColor(hasFix?"#0ff":"#888").drawImage(atob("DAyBAAACADgDuBOAeA4AzAHADgAAAA=="),x,y) + draw : (x,y) => g.setColor(hasFix?"#0f0":"#f88").drawImage(atob("DAwBEAKARAKQE4DwHkPqPRGKAEAA"),x,y) }; }, hrm:function() { var bpm = 0, bpmConfidence = 0; - var hasBPM = false; function onHRM(h) { if (h.confidence >= bpmConfidence) { bpmConfidence = h.confidence; bpm = h.bpm; - if (bpmConfidence) hasBPM = true; } } return { name : "HR", - fields : ["Heartrate"], + fields : ["Heartrate", "Confidence"], getValues : () => { - var r = [bpmConfidence?bpm:""]; + var r = [bpm,bpmConfidence]; bpm = 0; bpmConfidence = 0; return r; }, start : () => { - hasBPM = false; Bangle.on('HRM', onHRM); Bangle.setHRMPower(1,"recorder"); }, stop : () => { - hasBPM = false; Bangle.removeListener('HRM', onHRM); Bangle.setHRMPower(0,"recorder"); }, - draw : (x,y) => g.setColor(hasBPM?"#f00":"#888").drawImage(atob("DAyBAAAAAD/H/n/n/j/D/B+AYAAAAA=="),x,y) + draw : (x,y) => g.setColor(Bangle.isHRMOn()?"#f00":"#f88").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y) + }; + }, + bat:function() { + return { + name : "BAT", + fields : ["Battery Percentage", "Battery Voltage", "Charging"], + getValues : () => { + return [E.getBattery(), NRF.getBattery(), Bangle.isCharging()]; + }, + start : () => { + }, + stop : () => { + }, + draw : (x,y) => g.setColor(Bangle.isCharging() ? "#0f0" : "#ff0").drawImage(atob("DAwBAABgH4G4EYG4H4H4H4GIH4AA"),x,y) }; }, - temp:function() { var core = 0, skin = 0; var hasCore = false; @@ -106,7 +115,7 @@ hasCore = false; Bangle.removeListener('CoreTemp', onCore); }, - draw : (x,y) => g.setColor(hasCore?"#0f0":"#888").drawImage(atob("DAyBAAHh0js3EuDMA8A8AWBnDj9A8A=="),x,y) + draw : (x,y) => g.setColor(hasCore?"#0f0":"#8f8").drawImage(atob("DAwBAAAOAKPOfgZgZgZgZgfgPAAA"),x,y) }; }, steps:function() { @@ -121,7 +130,7 @@ }, start : () => { lastSteps = Bangle.getStepCount(); }, stop : () => {}, - draw : (x,y) => g.reset().drawImage(atob("DAyBAAADDHnnnnnnnnnnjDmDnDnAAA=="),x,y) + draw : (x,y) => g.reset().drawImage(atob("DAwBAAMMeeeeeeeecOMMAAMMMMAA"),x,y) }; } // TODO: recAltitude from pressure sensor @@ -138,7 +147,7 @@ } }) */ - require("Storage").list(/^.*\.recorder\.js$/).forEach(fn=>eval(fn)(recorders)); + require("Storage").list(/^.*\.recorder\.js$/).forEach(fn=>eval(require("Storage").read(fn))(recorders)); return recorders; } diff --git a/apps/ruuviwatch/ChangeLog b/apps/ruuviwatch/ChangeLog new file mode 100644 index 000000000..ebde871fa --- /dev/null +++ b/apps/ruuviwatch/ChangeLog @@ -0,0 +1,2 @@ +1.00: Hello Ruuvi Watch! +1.01: Clear gfx on startup. \ No newline at end of file diff --git a/apps/ruuviwatch/README.md b/apps/ruuviwatch/README.md new file mode 100644 index 000000000..bf4358267 --- /dev/null +++ b/apps/ruuviwatch/README.md @@ -0,0 +1,25 @@ +# Ruuvi Watch + +Watch the status of [RuuviTags](https://ruuvi.com) in range. + + - Id + - Temperature (°C) + - Humidity (%) + - Pressure (hPa) + - Battery voltage + + Also shows how "fresh" the data is (age of reading). + + ## Usage + + - Scans for devices when launched and every N seconds. + - Page trough devices with BTN1/BTN3. + - Trigger scan with BTN2. + +## Todo / ideas + + - Allow to "name" known devices + - Prevent flicker when updating + - Include more data + - Support older Ruuvi protocols + diff --git a/apps/ruuviwatch/metadata.json b/apps/ruuviwatch/metadata.json new file mode 100644 index 000000000..215df105a --- /dev/null +++ b/apps/ruuviwatch/metadata.json @@ -0,0 +1,14 @@ +{ "id": "ruuviwatch", + "name": "Ruuvi Watch", + "shortName":"Ruuvi Watch", + "icon": "ruuviwatch.png", + "version":"1.01", + "description": "Keep an eye on RuuviTag devices (https://ruuvi.com). Only shows RuuviTags using the v5 format.", + "readme":"README.md", + "tags": "bluetooth", + "supports": ["BANGLEJS"], + "storage": [ + {"name":"ruuviwatch.app.js","url":"ruuviwatch.app.js"}, + {"name":"ruuviwatch.img","url":"ruuviwatch.app-icon.js","evaluate":true} + ] +} diff --git a/apps/ruuviwatch/ruuviwatch.app-icon.js b/apps/ruuviwatch/ruuviwatch.app-icon.js new file mode 100644 index 000000000..7ed27ef6c --- /dev/null +++ b/apps/ruuviwatch/ruuviwatch.app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AH4A/ABMP/4ACCyIVDAAXwCyoYPIggAFCx4oEDBw/JJJguCBhAwLBZYjKBQQeGCIYNHB45bIBw4gIRgw+NC4wwJJ5YRLC5DzFCJBGMEYoSEFxoMEBQIXEF4gVFF5QcEC553JC5QRITgy/NVxIXGf5QlFIwy4IGBQuFC5JhGCwpGGERZOEBQ4MEDAwJJGAzdJCxLVJFxoYLCxoYICx6/GCqAA/AH4A/ACA")) \ No newline at end of file diff --git a/apps/ruuviwatch/ruuviwatch.app.js b/apps/ruuviwatch/ruuviwatch.app.js new file mode 100644 index 000000000..46218a323 --- /dev/null +++ b/apps/ruuviwatch/ruuviwatch.app.js @@ -0,0 +1,151 @@ +require("Storage").write("ruuviwatch.info",{ + "id":"ruuviwatch", + "name":"Ruuvi Watch", + "src":"ruuviwatch.app.js", + "icon":"ruuviwatch.img" + }); + + const lookup = {}; + const ruuvis = []; + let current = 0; + + function int2Hex (str) { + return ('0' + str.toString(16).toUpperCase()).slice(-2); + } + + function p(data) { + const OFFSET = 7; // 0-4 header, 5-6 Ruuvi id + const robject = {}; + robject.version = data[OFFSET]; + + let temperature = (data[OFFSET+1] << 8) | (data[OFFSET+2] & 0xff); + if (temperature > 32767) { + temperature -= 65534; + } + robject.temperature = temperature / 200.0; + + robject.humidity = (((data[OFFSET+3] & 0xff) << 8) | (data[OFFSET+4] & 0xff)) / 400.0; + robject.pressure = ((((data[OFFSET+5] & 0xff) << 8) | (data[OFFSET+6] & 0xff)) + 50000) / 100.0; + + let accelerationX = (data[OFFSET+7] << 8) | (data[OFFSET+8] & 0xff); + if (accelerationX > 32767) accelerationX -= 65536; // two's complement + robject.accelerationX = accelerationX / 1000.0; + + let accelerationY = (data[OFFSET+9] << 8) | (data[OFFSET+10] & 0xff); + if (accelerationY > 32767) accelerationY -= 65536; // two's complement + robject.accelerationY = accelerationY / 1000.0; + + let accelerationZ = (data[OFFSET+11] << 8) | (data[OFFSET+12] & 0xff); + if (accelerationZ > 32767) accelerationZ -= 65536; // two's complement + robject.accelerationZ = accelerationZ / 1000.0; + + const powerInfo = ((data[OFFSET+13] & 0xff) << 8) | (data[OFFSET+14] & 0xff); + robject.battery = ((powerInfo >>> 5) + 1600) / 1000.0; + robject.txPower = (powerInfo & 0b11111) * 2 - 40; + robject.movementCounter = data[OFFSET+15] & 0xff; + robject.measurementSequenceNumber = ((data[OFFSET+16] & 0xff) << 8) | (data[OFFSET+17] & 0xff); + + robject.mac = [ + int2Hex(data[OFFSET+18]), + int2Hex(data[OFFSET+19]), + int2Hex(data[OFFSET+20]), + int2Hex(data[OFFSET+21]), + int2Hex(data[OFFSET+22]), + int2Hex(data[OFFSET+23]) + ].join(':'); + + robject.name = "Ruuvi " + int2Hex(data[OFFSET+22]) + int2Hex(data[OFFSET+23]); + return robject; + } + + function getAge(created) { + const now = new Date().getTime(); + const ago = ((now - created) / 1000).toFixed(0); + return ago > 0 ? ago + "s ago" : "now"; + } + + function redraw() { + if (ruuvis.length > 0 && ruuvis[current]) { + const ruuvi = ruuvis[current]; + g.clear(); + g.setFontAlign(0,0); + g.setFont("Vector",12); + g.drawString(" (" + (current+1) + "/" + ruuvis.length + ")", g.getWidth()/2, 10); + g.setFont("Vector",20); + g.drawString(ruuvi.name, g.getWidth()/2, 30); + g.setFont("Vector",12); + const age = getAge(ruuvi.time); + if(age > (5*60)) { + g.setColor("#ff0000"); + } else if (age > 60) { + g.setColor("#f39c12"); + } else { + g.setColor("#2ecc71"); + } + g.drawString(age, g.getWidth()/2, 50); + g.setColor("#ffffff"); + g.setFont("Vector",60); + g.drawString(ruuvi.temperature.toFixed(2) + "°c", g.getWidth()/2, g.getHeight()/2); + g.setFontAlign(0,1); + g.setFont("Vector",20); + g.drawString(ruuvi.humidity + "% " + ruuvi.pressure + "hPa ", g.getWidth()/2, g.getHeight()-30); + g.setFont("Vector",12); + g.drawString(ruuvi.battery + "v", g.getWidth()/2, g.getHeight()-10); + } else { + g.clear(); + g.drawImage(require("Storage").read("ruuviwatch.img"), g.getWidth()/2-24, g.getHeight()/2-24); + g.setFontAlign(0,0); + g.setFont("Vector",16); + g.drawString("Looking for Ruuvi...", g.getWidth()/2, g.getHeight()/2 + 50); + } + } + + function scan() { + NRF.findDevices(function(devices) { + let foundNew = false; + devices.forEach(device => { + const data = p(device.data); + data.time = new Date().getTime(); + const idx = lookup[data.name]; + if (idx !== undefined) { + ruuvis[idx] = data; + } else { + lookup[data.name] = ruuvis.push(data)-1; + foundNew = true; + } + }); + redraw(); + if (foundNew) { + Bangle.buzz(); + g.flip(); + } + + }, {timeout : 2000, filters : [{ manufacturerData:{0x0499:{}} }] }); + } + + g.clear(); + g.drawImage(require("Storage").read("ruuviwatch.img"), g.getWidth()/2-24, g.getHeight()/2-24); + + var drawInterval = setInterval(redraw, 1000); + var scanInterval = setInterval(scan, 10000); + setWatch(() => { + current--; + if (current < 0) { + current = ruuvis.length-1; + } + redraw(); + }, BTN1, {repeat:true}); + + setWatch(() => { + scan(); + }, BTN2, {repeat:true}); + + setWatch(() => { + current++; + if (current >= ruuvis.length) { + current = 0; + } + redraw(); + }, BTN3, {repeat:true}); + + scan(); \ No newline at end of file diff --git a/apps/ruuviwatch/ruuviwatch.png b/apps/ruuviwatch/ruuviwatch.png new file mode 100644 index 000000000..3737a7e8c Binary files /dev/null and b/apps/ruuviwatch/ruuviwatch.png differ diff --git a/apps/widpedom/ChangeLog b/apps/widpedom/ChangeLog index 3483c3390..54f6b203b 100644 --- a/apps/widpedom/ChangeLog +++ b/apps/widpedom/ChangeLog @@ -21,3 +21,4 @@ Memory usage enhancements 0.20: Fix issue where step count would randomly reset 0.21: Memory usage improvements, fix widget initial width (fix #1170) +0.22: Fix 'stps' regression for 0.21 (fix #1233) diff --git a/apps/widpedom/metadata.json b/apps/widpedom/metadata.json index d7ed7fbd8..f49d3ba5b 100644 --- a/apps/widpedom/metadata.json +++ b/apps/widpedom/metadata.json @@ -1,7 +1,7 @@ { "id": "widpedom", "name": "Pedometer widget", - "version": "0.21", + "version": "0.22", "description": "Daily pedometer widget", "icon": "widget.png", "type": "widget", diff --git a/apps/widpedom/widget.js b/apps/widpedom/widget.js index 3a079c066..cc7fdb579 100644 --- a/apps/widpedom/widget.js +++ b/apps/widpedom/widget.js @@ -55,6 +55,7 @@ // add your widget WIDGETS["wpedom"]={area:"tl",width:0, getWidth:function() { + let stps = stp_today.toString(); let newWidth = 24; if (settings.hide) newWidth = 0; @@ -68,7 +69,6 @@ return newWidth; }, redraw:function() { // work out the width, and queue a full redraw if needed - let stps = stp_today.toString(); let newWidth = this.getWidth(); if (newWidth!=this.width) { // width has changed, re-layout all widgets