Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
448 lines (386 sloc)
12.5 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var SunCalc = require("https://raw.githubusercontent.com/mourner/suncalc/master/suncalc.js"); | |
require("f_latosmall").add(Graphics); | |
const storage = require('Storage'); | |
const locale = require("locale"); | |
const SETTINGS_FILE = "pastel.json"; | |
const LOCATION_FILE = "mylocation.json"; | |
const w = g.getWidth(); | |
const h = g.getHeight(); | |
let settings; | |
let location; | |
// variable for controlling idle alert | |
let lastStep = getTime(); | |
let lastStepTime = '??'; | |
let warned = 0; | |
let idle = false; | |
let IDLE_MINUTES = 26; | |
// cloud, sun, partSun, snow, rain, storm, error | |
// create 1 bit, max contrast, brightness set to 85 | |
var cloudIcon = require("heatshrink").decompress(atob("kEggIfcj+AAYM/8ADBuFwAYPAmADCCAMBwEf8ADBhFwg4aBnEPAYMYjAVBhgDDDoQDHCYc4jwDB+EP///FYIDBMTgA==")); | |
var sunIcon = require("heatshrink").decompress(atob("kEggILIgOAAZkDAYPAgeBwPAgIFBBgPhw4TBp/yAYMcnADBnEcAYMwhgDBsEGgE/AYP8AYYLDCYgbDEYYrD8fHIwI7CIYZLDL54AHA==")); | |
var sunPartIcon = require("heatshrink").decompress(atob("kEggIHEmADJjEwsEAjkw8EAh0B4EAg35wEAgP+CYMDwv8AYMDBAP2g8HgH+g0DBYMMgPwAYX8gOMEwMG3kAg8OvgSBjg2BgcYGQIcBAY5CBg0Av//HAM///4MYgNBEIMOCoUMDoUAnBwGkEA")); | |
var snowIcon = require("heatshrink").decompress(atob("kEggITQj/AAYM98ADBsEwAYPAjADCj+AgOAj/gAYMIuEHwEAjEPAYQVChk4AYQhCAYcYBYQTDnEPgEB+EH///IAQACE4IAB8EICIPghwDB4EeBYNAjgDBg8EAYQYCg4bCgZuFA==")); | |
var rainIcon = require("heatshrink").decompress(atob("kEggIPMh+AAYM/8ADBuFwAYPgmADB4EbAYOAj/ggOAhnwg4aBnAeCjEcCIMMjADCDoQDHjAPCnAXCuEP///8EDAYJECAAXBwkAgPDhwDBwUMgEEhkggEOjFgFgMQLYQAOA==")); | |
var errIcon = require("heatshrink").decompress(atob("kEggILIgOAAYsD4ADBg/gAYMGsADBhkwAYsYjADCjgDBmEMAYNxxwDBsOGAYPBwYDEgOBwOAgYDB4EDHYPAgwDBsADDhgDBFIcwjAHBjE4AYMcmADBhhNCKIcG/4AGOw4A==")); | |
// saves having to recode all the small font calls | |
function setSmallFont() { | |
g.setFontLatoSmall(); | |
} | |
function loadSettings() { | |
settings = require("Storage").readJSON(SETTINGS_FILE,1)||{}; | |
settings.grid = settings.grid||false; | |
settings.font = settings.font||"Lato"; | |
settings.idle_check = settings.idle_check||true; | |
} | |
// requires the myLocation app | |
function loadLocation() { | |
location = require("Storage").readJSON(LOCATION_FILE,1)||{}; | |
location.lat = location.lat||51.5072; | |
location.lon = location.lon||0.1276; | |
location.location = location.location||"London"; | |
} | |
function extractTime(d){ | |
var h = d.getHours(), m = d.getMinutes(); | |
return(("0"+h).substr(-2) + ":" + ("0"+m).substr(-2)); | |
} | |
var sunRise = "00:00"; | |
var sunSet = "00:00"; | |
var drawCount = 0; | |
function updateSunRiseSunSet(now, lat, lon, line){ | |
// get today's sunlight times for lat/lon | |
var times = SunCalc.getTimes(new Date(), lat, lon); | |
// format sunrise time from the Date object | |
sunRise = extractTime(times.sunrise); | |
sunSet = extractTime(times.sunset); | |
} | |
function loadFonts() { | |
// load font files based on settings.font | |
if (settings.font == "Architect") | |
require("f_architect").add(Graphics); | |
else if (settings.font == "GochiHand") | |
require("f_gochihand").add(Graphics); | |
else if (settings.font == "CabinSketch") | |
require("f_cabin").add(Graphics); | |
else if (settings.font == "Orbitron") | |
require("f_orbitron").add(Graphics); | |
else if (settings.font == "Monoton") | |
require("f_monoton").add(Graphics); | |
else if (settings.font == "Elite") | |
require("f_elite").add(Graphics); | |
else | |
require("f_lato").add(Graphics); | |
} | |
function getSteps() { | |
try { | |
return Bangle.getHealthStatus("day").steps; | |
} catch (e) { | |
if (WIDGETS.wpedom !== undefined) | |
return WIDGETS.wpedom.getSteps(); | |
else | |
return '???'; | |
} | |
} | |
const infoData = { | |
ID_BLANK: { calc: () => '' }, | |
ID_DATE: { calc: () => {var d = (new Date()).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} }, | |
ID_DAY: { calc: () => {var d = require("locale").dow(new Date()).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} }, | |
ID_SR: { calc: () => 'Sunrise: ' + sunRise }, | |
ID_SS: { calc: () => 'Sunset: ' + sunSet }, | |
ID_STEP: { calc: () => 'Steps: ' + getSteps() }, | |
ID_LAST: { calc: () => 'Last Step: ' + lastStepTime }, | |
ID_BATT: { calc: () => 'Battery: ' + E.getBattery() + '%' }, | |
ID_MEM: { calc: () => {var val = process.memory(false); return 'Ram: ' + Math.round(val.usage*100/val.total) + '%';} }, | |
ID_ID: { calc: () => {var val = NRF.getAddress().split(':'); return 'Id: ' + val[4] + val[5];} }, | |
ID_FW: { calc: () => 'Fw: ' + process.env.VERSION } | |
}; | |
const infoList = Object.keys(infoData).sort(); | |
let infoMode = infoList[0]; | |
function nextInfo() { | |
let idx = infoList.indexOf(infoMode); | |
if (idx > -1) { | |
if (idx === infoList.length - 1) infoMode = infoList[0]; | |
else infoMode = infoList[idx + 1]; | |
} | |
} | |
function prevInfo() { | |
let idx = infoList.indexOf(infoMode); | |
if (idx > -1) { | |
if (idx === 0) infoMode = infoList[infoList.length - 1]; | |
else infoMode = infoList[idx - 1]; | |
} | |
} | |
/** | |
Choose weather icon to display based on condition. | |
Based on function from the Bangle weather app so it should handle all of the conditions | |
sent from gadget bridge. | |
*/ | |
function chooseIcon(condition) { | |
condition = condition.toLowerCase(); | |
if (condition.includes("thunderstorm")) return stormIcon; | |
if (condition.includes("freezing")||condition.includes("snow")|| | |
condition.includes("sleet")) { | |
return snowIcon; | |
} | |
if (condition.includes("drizzle")|| | |
condition.includes("shower")) { | |
return rainIcon; | |
} | |
if (condition.includes("rain")) return rainIcon; | |
if (condition.includes("clear")) return sunIcon; | |
if (condition.includes("few clouds")) return partSunIcon; | |
if (condition.includes("scattered clouds")) return cloudIcon; | |
if (condition.includes("clouds")) return cloudIcon; | |
if (condition.includes("mist") || | |
condition.includes("smoke") || | |
condition.includes("haze") || | |
condition.includes("sand") || | |
condition.includes("dust") || | |
condition.includes("fog") || | |
condition.includes("ash") || | |
condition.includes("squalls") || | |
condition.includes("tornado")) { | |
return cloudIcon; | |
} | |
return cloudIcon; | |
} | |
/** | |
Get weather stored in json file by weather app. | |
*/ | |
function getWeather() { | |
let jsonWeather = storage.readJSON('weather.json'); | |
return jsonWeather; | |
} | |
function draw() { | |
if (!idle) | |
drawClock(); | |
else | |
drawIdle(); | |
queueDraw(); | |
} | |
function drawClock() { | |
var d = new Date(); | |
var da = d.toString().split(" "); | |
var time = da[4].substr(0,5); | |
var hh = da[4].substr(0,2); | |
var mm = da[4].substr(3,2); | |
var day = da[0]; | |
var month_day = da[1] + " " + da[2]; | |
// fix hh for 12hr clock | |
var h2 = "0" + parseInt(hh) % 12 || 12; | |
if (parseInt(hh) > 12) | |
hh = h2.substr(h2.length -2); | |
var x = (g.getWidth()/2); | |
var y = (g.getHeight()/3); | |
var weatherJson = getWeather(); | |
var w_temp; | |
var w_icon; | |
var w_wind; | |
if (settings.weather && weatherJson && weatherJson.weather) { | |
var currentWeather = weatherJson.weather; | |
const temp = locale.temp(currentWeather.temp-273.15).match(/^(\D*\d*)(.*)$/); | |
w_temp = temp[1] + " " + temp[2]; | |
w_icon = chooseIcon(currentWeather.txt); | |
const wind = locale.speed(currentWeather.wind).match(/^(\D*\d*)(.*)$/); | |
w_wind = wind[1] + " " + wind[2] + " " + (currentWeather.wrose||'').toUpperCase(); | |
} else { | |
w_temp = "Err"; | |
w_wind = "???"; | |
w_icon = errIcon; | |
} | |
g.reset(); | |
g.setColor(g.theme.bg); | |
g.fillRect(Bangle.appRect); | |
// draw a grid like graph paper | |
if (settings.grid && process.env.HWVERSION !=1) { | |
g.setColor("#0f0"); | |
for (var gx=20; gx <= w; gx += 20) | |
g.drawLine(gx, 30, gx, h - 24); | |
for (var gy=30; gy <= h - 24; gy += 20) | |
g.drawLine(0, gy, w, gy); | |
} | |
g.setColor(g.theme.fg); | |
// draw weather line | |
if (settings.weather) { | |
g.drawImage(w_icon, (w/2) - 40, 24); | |
g.setFontLatoSmall(); | |
g.setFontAlign(-1,0); // left aligned | |
if (drawCount % 2 == 0) | |
g.drawString(w_temp, (w/2) + 6, 24 + ((y - 24)/2)); | |
else | |
g.drawString( (w_wind.split(' ').slice(0, 2).join(' ')), (w/2) + 6, 24 + ((y - 24)/2)); | |
// display first 2 words of the wind string eg '4 mph' | |
} | |
if (settings.font == "Architect") | |
g.setFontArchitect(); | |
else if (settings.font == "GochiHand") | |
g.setFontGochiHand(); | |
else if (settings.font == "CabinSketch") | |
g.setFontCabinSketch(); | |
else if (settings.font == "Orbitron") | |
g.setFontOrbitron(); | |
else if (settings.font == "Monoton") | |
g.setFontMonoton(); | |
else if (settings.font == "Elite") | |
g.setFontSpecialElite(); | |
else | |
g.setFontLato(); | |
g.setFontAlign(1,-1); // right aligned | |
g.drawString(hh, x - 6, y); | |
g.setFontAlign(-1,-1); // left aligned | |
g.drawString(mm, x + 6, y); | |
// for the colon | |
g.setFontAlign(0,-1); // centre aligned | |
g.drawString(":", x,y); | |
g.setFontLatoSmall(); | |
g.setFontAlign(0, -1); | |
g.drawString((infoData[infoMode].calc()), w/2, h - 24 - 24); | |
// recalc sunrise / sunset every hour | |
if (drawCount % 60 == 0) | |
updateSunRiseSunSet(new Date(), location.lat, location.lon); | |
drawCount++; | |
queueDraw(); | |
} | |
///////////////// IDLE TIMER ///////////////////////////////////// | |
function log_debug(o) { | |
//print(o); | |
} | |
function drawIdle() { | |
let mins = Math.round((getTime() - lastStep) / 60); | |
g.reset(); | |
g.setColor(g.theme.bg); | |
g.fillRect(Bangle.appRect); | |
g.setColor(g.theme.fg); | |
setSmallFont(); | |
g.setFontAlign(0, 0); | |
g.drawString('Last step was', w/2, (h/3)); | |
g.drawString(mins + ' minutes ago', w/2, 20+(h/3)); | |
dismissBtn.draw(); | |
} | |
/////////////// BUTTON CLASS /////////////////////////////////////////// | |
// simple on screen button class | |
function BUTTON(name,x,y,w,h,c,f,tx) { | |
this.name = name; | |
this.x = x; | |
this.y = y; | |
this.w = w; | |
this.h = h; | |
this.color = c; | |
this.callback = f; | |
this.text = tx; | |
} | |
// if pressed the callback | |
BUTTON.prototype.check = function(x,y) { | |
//console.log(this.name + ":check() x=" + x + " y=" + y +"\n"); | |
if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) { | |
log_debug(this.name + ":callback\n"); | |
this.callback(); | |
return true; | |
} | |
return false; | |
}; | |
BUTTON.prototype.draw = function() { | |
g.setColor(this.color); | |
g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h); | |
g.setColor("#000"); // the icons and boxes are drawn black | |
setSmallFont(); | |
g.setFontAlign(0, 0); | |
g.drawString(this.text, (this.x + this.w/2), (this.y + this.h/2)); | |
g.drawRect(this.x, this.y, (this.x + this.w), (this.y + this.h)); | |
}; | |
function dismissPrompt() { | |
idle = false; | |
warned = false; | |
lastStep = getTime(); | |
Bangle.buzz(100); | |
draw(); | |
} | |
var dismissBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", dismissPrompt, "Dismiss"); | |
Bangle.on('touch', function(button, xy) { | |
if (idle && dismissBtn.check(xy.x, xy.y)) return; | |
}); | |
// if we get a step then we are not idle | |
Bangle.on('step', s => { | |
setLastStepTime(); | |
lastStep = getTime(); | |
// redraw if we had been idle | |
if (idle == true) { | |
dismissPrompt(); | |
} | |
idle = false; | |
warned = 0; | |
}); | |
function setLastStepTime() { | |
var date = new Date(); | |
lastStepTime = require("locale").time(date,1); | |
} | |
function checkIdle() { | |
if (!settings.idle_check) { | |
idle = false; | |
warned = false; | |
return; | |
} | |
let hour = (new Date()).getHours(); | |
let active = (hour >= 9 && hour < 21); | |
//let active = true; | |
let dur = getTime() - lastStep; | |
if (active && dur > IDLE_MINUTES * 60) { | |
drawIdle(); | |
if (warned++ < 3) { | |
buzzer(warned); | |
log_debug("checkIdle: warned=" + warned); | |
Bangle.setLocked(false); | |
} | |
idle = true; | |
} else { | |
idle = false; | |
warned = 0; | |
} | |
} | |
setLastStepTime(); | |
// timeout for multi-buzzer | |
var buzzTimeout; | |
// n buzzes | |
function buzzer(n) { | |
log_debug("buzzer n=" + n); | |
if (n-- < 1) return; | |
Bangle.buzz(250); | |
if (buzzTimeout) clearTimeout(buzzTimeout); | |
buzzTimeout = setTimeout(function() { | |
buzzTimeout = undefined; | |
buzzer(n); | |
}, 500); | |
} | |
/////////////////////////////////////////////////////////////////////////////// | |
// timeout used to update every minute | |
var drawTimeout; | |
// schedule a draw for the next minute | |
function queueDraw() { | |
if (drawTimeout) clearTimeout(drawTimeout); | |
drawTimeout = setTimeout(function() { | |
drawTimeout = undefined; | |
prevInfo(); | |
checkIdle(); | |
draw(); | |
}, 60000 - (Date.now() % 60000)); | |
} | |
// Stop updates when LCD is off, restart when on | |
Bangle.on('lcdPower',on=>{ | |
if (on) { | |
draw(); // draw immediately, queue redraw | |
} else { // stop draw timer | |
if (drawTimeout) clearTimeout(drawTimeout); | |
drawTimeout = undefined; | |
} | |
}); | |
Bangle.setUI("clockupdown", btn=> { | |
if (btn<0) prevInfo(); | |
if (btn>0) nextInfo(); | |
draw(); | |
}); | |
loadSettings(); | |
loadFonts(); | |
loadLocation(); | |
g.clear(); | |
Bangle.loadWidgets(); | |
Bangle.drawWidgets(); | |
draw(); |