(() => { // Current shown notification, saved for dismissing. var currentNot = null; // Music handling const state = { music: "stop", musicInfo: { artist: "", album: "", track: "" }, scrollPos: 0 }; // activity reporting var currentSteps = 0, lastSentSteps=0; var activityInterval; var hrmTimeout; function settings() { let settings = require('Storage').readJSON("gbridge.json", true) || {}; if (!("showIcon" in settings)) { settings.showIcon = true; } return settings; } function gbSend(message) { Bluetooth.println(""); Bluetooth.println(JSON.stringify(message)); } function prettifyNotificationEvent(event) { switch (event.src) { case "ALARMCLOCKRECEIVER": return { id: event.id, title: event.title || "Alarm: "+require('locale').time(new Date(), true), body: event.body, // same icon as apps/alarm/app-icon.js icon: require("heatshrink").decompress(atob("mEwwkGswAhiMRCCAREAo4eHBIQLEAgwYHsIJDiwHB5gACBpIhHCoYZEGA4gFCw4ABGA4HEjgXJ4IXGAwcUB4VEmf//8zogICoJIFAodMBoNDCoIADmgJB4gXIFwXDCwoABngwFC4guB4k/CQXwh4EC+YMCC44iBp4qDC4n/+gNBC41sEIJCEC4v/GAPGC4dhXYRdFC4xhCCYIXCdQRdDC5HzegQXCsxGHC45IDCwQXCUgwXHJAIXGRogXJSIIXcOw4XIPAYXcBwv/mEDBAwXOgtQC65QGC5vzoEAJAx3Nmk/mEABIiPN+dDAQIwFC4zXGFwKRCGAjvMFwQECGAgXI4YuGGAUvAgU8C4/EFwwGCAgdMC4p4EFwobFOwoXDJAIoEAApGBC4xIEABJGHGAapEAAqNBFwwXD4heI+YuBC5BIBVQhdHIw4wD5inFS4IKCCxFmigNCokzCoMzogICoIWIsMRjgPCAA3BiMWC48RBQIXJEgMRFxAJCCw4lEC44IECooOIBAaBJKwhgIAH4ACA==")), }; default: return event; } } function updateMusic(options){ if (state.music === "play") { require("notify").show(Object.assign({ size:40, id:"music", render:a => { if (a.h>200) { // large: // [icon] // [ ] // // ------------- middle of screen // const iconSize = 24*3; // 24x24px, scale 3 let x = a.x, y = a.y+a.h/2, // we use coords relative to vertical middle w=a.w,h=a.h; // try to fit musicInfo property into width const fitFont = (prop,max) => { if (!(prop in state.musicInfo)) return max; let size = Math.floor(w/(state.musicInfo[prop].length*6)); if (size<1) {size=1} if (size>max) {size=max} return size; } let aSize = fitFont('artist',3); // TODO: split long title over multiple lines instead let tSize = fitFont('track',2); let bSize = fitFont('album',2); g.setColor(-1); // TODO: use a nicer large icon? g.drawImage( require("heatshrink").decompress(atob("jEYwILI/EAv/8gP/ARcMgOAASN8h+A/kfwP8n4CD/E/gHgjg/HA=")), x+(w-iconSize)/2, y-(iconSize+aSize*8)-12, {scale: 3}); // artist: centered above middle g.setFontAlign(0, 1).setFont("6x8", aSize).drawString(state.musicInfo.artist, x+w/2, y-4); // title: left-aligned below middle g.setFontAlign(-1, -1).setFont("6x8", tSize).drawString(state.musicInfo.track, x, y+4); // album: centered at bottom if (state.musicInfo.album) { // note: using a.y rather than y g.setFontAlign(0, 1).setFont("6x8", bSize).drawString(state.musicInfo.album, x+w/2, a.y+h); } } else { // regular size: // [icon] <artist> // [ ] <title> const size=40, iconSize = 24; let x = a.x, y = a.y+(a.h-size)/2; // center vertically in available area g.setColor(-1); g.drawImage( require("heatshrink").decompress(atob("jEYwILI/EAv/8gP/ARcMgOAASN8h+A/kfwP8n4CD/E/gHgjg/HA=")), x+8, y+8); g.setFontAlign(-1, -1); x += iconSize+16; g.setFont("4x6", 2).drawString(state.musicInfo.artist, x, y+8); g.setFont("6x8", 1).drawString(state.musicInfo.track, x, y+22); } }}, options)); } if (state.music === "pause") { require("notify").hide("music"); } } function handleActivityEvent(event) { var s = settings(); // handle setting activity interval if (s.activityInterval===undefined || s.activityInterval<30) s.activityInterval = 3*60; // 3 minutes default if (event.int) { if (event.int<30) event.int = 30; // min 30 secs s.activityInterval = event.int; require('Storage').writeJSON("gbridge.json", s); } // set up interval/HRM to handle activity data var interval = s.activityInterval; var realtime = event.hrm || event.stp; if (activityInterval) clearInterval(activityInterval); activityInterval = undefined; if (s.hrm) Bangle.setHRMPower(1,"gbr"); if (s.hrm) { if (realtime) { // if realtime reporting, leave HRM on and use that to trigger events hrmTimeout = undefined; } else { // else trigger it manually every so often hrmTimeout = 5; activityInterval = setInterval(function() { hrmTimeout = 5; Bangle.setHRMPower(1,"gbr"); }, interval*1000); } } else { // no HRM - manually push data if (realtime) interval=10; activityInterval = setInterval(function() { sendActivity(-1); }, interval*1000); } } var _GB = global.GB; global.GB = (event) => { switch (event.t) { case "notify": currentNot = prettifyNotificationEvent(event); currentNot.onHide = function() { // when notification hidden, remove from phone gbSend({ t:"notify", n:"DISMISS", id:currentNot.id }); }; require("notify").show(currentNot); if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) { Bangle.buzz(); } break; case "notify-": currentNot.t = "notify"; currentNot.n = "DISMISS"; gbSend(currentNot); currentNot = null; require("notify").hide(event); break; case "musicinfo": state.musicInfo = event; updateMusic({on: false}); break; case "musicstate": if (state.music !== event.state) { state.music = event.state updateMusic({on: true}); } break; case "call": var note = { size: 55, title: event.name, id: "call", body: event.number, icon:require("heatshrink").decompress(atob("jEYwIMJj4CCwACJh4CCCIMOAQMGAQMHAQMDAQMBCIMB4PwgHz/EAn4CBj4CBg4CBgACCAAw="))} if (event.cmd === "incoming" || event.cmd === "") { require("notify").show(note); if (!(require('Storage').readJSON('setting.json',1)||{}).quiet) { Bangle.buzz(); } } else if (event.cmd === "start") { require("notify").show(Object.assign(note, { bgColor : "#008000", titleBgColor : "#00C000", body: "In progress: "+event.number})); } else if (event.cmd === "end") { require("notify").show(Object.assign(note, { bgColor : "#800000", titleBgColor : "#C00000", body: "Ended: "+event.number})); setTimeout(function() { require("notify").hide({ id: "call" }); }, 2000); } break; case "find": if (state.find) { clearInterval(state.find); delete state.find; } if (event.n) // Ignore quiet mode: we always want to find our watch state.find = setInterval(_=>{ Bangle.buzz(); setTimeout(_=>Bangle.beep(), 1000); },2000); break; case "act": handleActivityEvent(event); break; } if(_GB)setTimeout(_GB,0,event); }; Bangle.on("swipe", (dir) => { if (state.music === "play") { const command = dir > 0 ? "next" : "previous" gbSend({ t: "music", n: command }); } }); function draw() { g.setColor(-1); if (NRF.getSecurityStatus().connected) g.drawImage(require("heatshrink").decompress(atob("i0WwgHExAABCIwJCBYwJEBYkIBQ2ACgvzCwoECx/z/AKDD4WD+YLBEIYKCx//+cvnAKCBwU/mc4/8/HYv//Ev+Y4EEAePn43DBQkzn4rCEIoABBIwKHO4cjmczK42I6mqlqEEBQeIBQaDED4IgDUhi6KaBbmIA==")), this.x + 1, this.y + 1); else g.drawImage(require("heatshrink").decompress(atob("i0WwQFC1WgAgYFDAgIFClQFCwEK1W/AoIPB1f+CAMq1f7/WqwQPB/fq1Gq1/+/4dC/2/CAIaB/YbBAAO///qAoX/B4QbBDQQ7BDQQrBAAWoIIIACIIIVC0ECB4cACAZiBAoRtCAoIDBA")), this.x + 1, this.y + 1); } function changedConnectionState() { WIDGETS["gbridgew"].draw(); g.flip(); // turns screen on } function reload() { NRF.removeListener("connect", changedConnectionState); NRF.removeListener("disconnect", changedConnectionState); if (settings().showIcon) { WIDGETS["gbridgew"].width = 24; WIDGETS["gbridgew"].draw = draw; NRF.on("connect", changedConnectionState); NRF.on("disconnect", changedConnectionState); } else { WIDGETS["gbridgew"].width = 0; WIDGETS["gbridgew"].draw = ()=>{}; } } function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); } // Send a summary of activity to Gadgetbridge function sendActivity(hrm) { var steps = currentSteps - lastSentSteps; lastSentSteps = currentSteps; gbSend({ t: "act", stp: steps, hrm:hrm, rt:1 }); } // Battery monitor NRF.on("connect", () => setTimeout(sendBattery, 2000)); Bangle.on("charging", sendBattery); setInterval(sendBattery, 10*60*1000); sendBattery(); // Activity monitor Bangle.on("step", s => { if (!lastSentSteps) lastSentSteps = s-1; currentSteps = s; }); Bangle.on('HRM',function(hrm) { var ok = hrm.confidence>80; if (hrmTimeout!==undefined) hrmTimeout--; if (ok || hrmTimeout<=0) { if (hrmTimeout!==undefined) Bangle.setHRMPower(0,"gbr"); sendActivity(hrm.confidence>20 ? hrm.bpm : -1); } }); handleActivityEvent({}); // kicks off activity reporting // Finally add widget WIDGETS["gbridgew"] = {area: "tl", width: 24, draw: draw, reload: reload}; reload(); })();