Merge branch 'master' of github.com:espruino/BangleApps

pull/2570/head
Gordon Williams 2023-02-09 10:43:56 +00:00
commit 912f7dfc41
30 changed files with 626 additions and 467 deletions

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Don't fire if the app uses swipes already.

17
apps/backswipe/README.md Normal file
View File

@ -0,0 +1,17 @@
Service that allows you to use an app's back button using left to right swipe gesture.
## Settings
Mode: Blacklist/Whitelist/Always On/Disabled
App List: Black-/whitelisted apps
Standard # of swipe handlers: 0-10 (Default: 0, must be changed for backswipe to work at all)
Standard # of drag handlers: 0-10 (Default: 0, must be changed for backswipe to work at all)
Standard # of handlers settings are used to fine tune when backswipe should trigger the back function. E.g. when using a keyboard that works on drags, we don't want the backswipe to trigger when we just wanted to select a letter. This might not be able to cover all cases however.
## Creator
Kedlub
## Contributors
thyttan

View File

@ -15,18 +15,28 @@
var currentFile = global.__FILE__ || "";
if(global.BACK) delete global.BACK;
if (global.BACK) delete global.BACK;
if (options && options.back && enabledForApp(currentFile)) {
global.BACK = options.back;
}
setUI(mode, cb);
};
function goBack(lr, ud) {
function countHandlers(eventType) {
if (Bangle["#on"+eventType] === undefined) {
return 0;
} else if (Bangle["#on"+eventType] instanceof Array) {
return Bangle["#on"+eventType].length;
} else if (Bangle["#on"+eventType] !== undefined) {
return 1;
}
}
function goBack(lr, _) {
// if it is a left to right swipe
if (lr === 1) {
// if we're in an app that has a back button, run the callback for it
if (global.BACK) {
if (global.BACK && countHandlers("swipe")<=settings.standardNumSwipeHandlers && countHandlers("drag")<=settings.standardNumDragHandlers) {
global.BACK();
}
}

View File

@ -1,7 +1,7 @@
{ "id": "backswipe",
"name": "Back Swipe",
"shortName":"BackSwipe",
"version":"0.01",
"version":"0.02",
"description": "Service that allows you to use an app's back button using left to right swipe gesture",
"icon": "app.png",
"tags": "back,gesture,swipe",

View File

@ -4,19 +4,21 @@
// Apps is an array of app info objects, where all the apps that are there are either blocked or allowed, depending on the mode
var DEFAULTS = {
'mode': 0,
'apps': []
'apps': [],
'standardNumSwipeHandlers': 0,
'standardNumDragHandlers': 0
};
var settings = {};
var loadSettings = function() {
settings = require('Storage').readJSON(FILE, 1) || DEFAULTS;
}
};
var saveSettings = function(settings) {
require('Storage').write(FILE, settings);
}
};
// Get all app info files
var getApps = function() {
var apps = require('Storage').list(/\.info$/).map(appInfoFileName => {
@ -35,8 +37,8 @@
return 0;
});
return apps;
}
};
var showMenu = function() {
var menu = {
'': { 'title': 'Backswipe' },
@ -55,11 +57,31 @@
},
'App List': () => {
showAppSubMenu();
},
'Standard # of swipe handlers' : { // If more than this many handlers are present backswipe will not go back
value: 0|settings.standardNumSwipeHandlers,
min: 0,
max: 10,
format: v=>v,
onchange: v => {
settings.standardNumSwipeHandlers = v;
saveSettings(settings);
},
},
'Standard # of drag handlers' : { // If more than this many handlers are present backswipe will not go back
value: 0|settings.standardNumDragHandlers,
min: 0,
max: 10,
format: v=>v,
onchange: v => {
settings.standardNumDragHandlers = v;
saveSettings(settings);
},
}
};
E.showMenu(menu);
}
};
var showAppSubMenu = function() {
var menu = {
@ -101,4 +123,4 @@
loadSettings();
showMenu();
})
})

View File

@ -64,3 +64,4 @@
0.55: Add toLocalISOString polyfill for pre-2v15 firmwares
Only add boot info comments if settings.bootDebug was set
If settings.bootDebug is set, output timing for each section of .boot0
0.56: Settings.log = 0,1,2,3 for off,display, log, both

View File

@ -32,14 +32,12 @@ if (s.ble!==false) {
boot += `bleServiceOptions.hid=Bangle.HID;\n`;
}
}
if (s.log==2) { // logging to file
boot += `_DBGLOG=require("Storage").open("log.txt","a");
`;
} if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
if (s.log==2) boot += `_DBGLOG=require("Storage").open("log.txt","a");
LoopbackB.on('data',function(d) {_DBGLOG.write(d);Terminal.write(d);});
// settings.log 0-off, 1-display, 2-log, 3-both
if (s.blerepl===false) { // If not programmable, force terminal off Bluetooth
if (s.log>=2) { boot += `_DBGLOG=require("Storage").open("log.txt","a");
LoopbackB.on('data',function(d) {_DBGLOG.write(d);${(s.log==3)?"Terminal.write(d);":""}});
LoopbackA.setConsole(true);\n`;
else if (s.log) boot += `Terminal.setConsole(true);\n`; // if showing debug, force REPL onto terminal
} else if (s.log==1) boot += `Terminal.setConsole(true);\n`; // if showing debug, force REPL onto terminal
else boot += `E.setConsole(null,{force:true});\n`; // on new (2v05+) firmware we have E.setConsole which allows a 'null' console
/* If not programmable add our own handler for Bluetooth data
to allow Gadgetbridge commands to be received*/
@ -56,10 +54,10 @@ Bluetooth.on('line',function(l) {
try { global.GB(JSON.parse(l.slice(3,-1))); } catch(e) {}
});\n`;
} else {
if (s.log==2) boot += `_DBGLOG=require("Storage").open("log.txt","a");
LoopbackB.on('data',function(d) {_DBGLOG.write(d);Terminal.write(d);});
if (s.log>=2) boot += `_DBGLOG=require("Storage").open("log.txt","a");
LoopbackB.on('data',function(d) {_DBGLOG.write(d);${(s.log==3)?"Terminal.write(d);":""}});
if (!NRF.getSecurityStatus().connected) LoopbackA.setConsole();\n`;
else if (s.log) boot += `if (!NRF.getSecurityStatus().connected) Terminal.setConsole();\n`; // if showing debug, put REPL on terminal (until connection)
else if (s.log==1) boot += `if (!NRF.getSecurityStatus().connected) Terminal.setConsole();\n`; // if showing debug, put REPL on terminal (until connection)
else boot += `Bluetooth.setConsole(true);\n`; // else if no debug, force REPL to Bluetooth
}
// we just reset, so BLE should be on.

View File

@ -1,7 +1,7 @@
{
"id": "boot",
"name": "Bootloader",
"version": "0.55",
"version": "0.56",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png",
"type": "bootloader",

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Get weather from weather.json
0.03: Address unexpected undefined when reading weather.json

View File

@ -326,7 +326,7 @@ function setWeather() {
function readWeather() {
var weatherJson = require("Storage").readJSON('weather.json', 1);
// save updated weather data if available and it has been an hour since last updated
if (weatherJson !== undefined && (data.time === undefined || (data.time + 3600000) < weatherJson.weather.time)) {
if (weatherJson && weatherJson.weather && weatherJson.weather.time && (data.time === undefined || (data.time + 3600000) < weatherJson.weather.time)) {
data = {
time: weatherJson.weather.time,
temp: weatherJson.weather.temp,

View File

@ -2,7 +2,7 @@
"id": "mtnclock",
"name": "Mountain Pass Clock",
"shortName": "Mtn Clock",
"version": "0.02",
"version": "0.03",
"description": "A clock that changes scenery based on time and weather.",
"readme":"README.md",
"icon": "app.png",

View File

@ -62,3 +62,4 @@
0.55: More strings tagged for automatic translation.
0.56: make System menu items shorter and more consistant, Eg 'Clock', intead
of 'Select Clock'
0.57: Settings.log = 0,1,2,3 for off,display,log,both

View File

@ -56,9 +56,10 @@ The exact effects depend on the app. In general the watch will not wake up by i
* **Debug Info** should debug info be shown on the watch's screen or not?
* `Hide` (default) do not show debug information
* `Show` Show on the Bangle's screen (when not connected to Bluetooth or `Programmable:off`)
* `Off` (default) do not show debug information
* `Display` Show on the Bangle's screen (when not connected to Bluetooth or `Programmable:off`)
* `Log` Show on the Bangle's screen **and** write to a file called `log.txt` on Storage (when not connected to Bluetooth or `Programmable:off`). Warning - this file is appended to so may grow to be large if this is left enabled.
* `Both` Log and display on Bangle's screen
* **Compact Storage** Removes deleted/old files from Storage - this will speed up your Bangle.js
* **Rewrite Settings** Should not normally be required, but if `.boot0` has been deleted/corrupted (and so no settings are being loaded) this will fix it.
* **Flatten Battery** Turns on all devices and draws as much power as possible, attempting to flatten the Bangle.js battery. This can still take 5+ hours.

View File

@ -1,7 +1,7 @@
{
"id": "setting",
"name": "Settings",
"version": "0.56",
"version": "0.57",
"description": "A menu for setting up Bangle.js",
"icon": "settings.png",
"tags": "tool,system",

View File

@ -557,11 +557,11 @@ function showUtilMenu() {
var menu = {
'': { 'title': /*LANG*/'Utilities' },
'< Back': ()=>showMainMenu(),
/*LANG*/'Debug Info': {
value: E.clip(0|settings.log,0,2),
/*LANG*/'Debug': {
value: E.clip(0|settings.log,0,3),
min: 0,
max: 2,
format: v => [/*LANG*/"Hide",/*LANG*/"Show",/*LANG*/"Log"][E.clip(0|v,0,2)],
max: 3,
format: v => [/*LANG*/"Off",/*LANG*/"Display",/*LANG*/"Log", /*LANG*/"Both"][E.clip(0|v,0,3)],
onchange: v => {
settings.log = v;
updateSettings();

View File

@ -13,6 +13,7 @@ function draw() {
g.setFontAlign(0, 0);
g.setColor(g.theme.fg);
g.drawString(timeStr, w/2, h/2);
console.log(timeStr + ", simplest");
queueDraw();
}

View File

@ -14,3 +14,6 @@
0.11: Minor tweaks
0.12: Support javascript command to execute as defined in scheduler 'js' configuration
0.13: Fix dated events alarm on wrong date
0.14: Reduce update interval of current time when seconds are not shown
Limit logging on Bangle.js 1 to one day due to low memory
Add plot logged data to settings

View File

@ -23,6 +23,11 @@ Replacing the watch strap with a more comfortable one (e.g. made of nylon) is re
## Logging
For each day of month (1..31) the ESS states are logged. An entry will be overwritten in the next month, e.g. an entry on the 4th May will overwrite an entry on the 4th April.
The logs can be viewed with the download button:
On Bangle.js 1 only one day is logged due to low memory.
The logs can be plotted from the settings menu:
![](screenshot.jpg)
![](screenshot_log.png)
The logs can also be viewed with the download button in the App Loader:
![](interface.jpg)

View File

@ -14,6 +14,7 @@ const active = alarms.filter(alarm => require("sched").getTimeToAlarm(alarm));
const schedSettings = require("sched").getSettings();
let buzzCount = schedSettings.buzzCount;
let logs = [];
let drawTimeTimeout;
// Sleep/Wake detection with Estimation of Stationary Sleep-segments (ESS):
// Marko Borazio, Eugen Berlin, Nagihan Kücükyildiz, Philipp M. Scholl and Kristof Van Laerhoven, "Towards a Benchmark for Wearable Sleep Analysis with Inertial Wrist-worn Sensing Units", ICHI 2014, Verona, Italy, IEEE Press, 2014.
@ -26,7 +27,7 @@ const nomothresh=0.023; // Original implementation: 6, resolution 11 bit, scale
const sleepthresh=600;
var ess_values = [];
var slsnds = 0;
function calc_ess(acc_magn) {
function calc_ess(acc_magn) {"ram"
ess_values.push(acc_magn);
if (ess_values.length == winwidth) {
@ -90,10 +91,12 @@ function drawApp() {
layout.alarm_date.label = `${LABEL_WAKEUP_TIME}: ${alarmHour}:${alarmMinute}`;
layout.render();
function drawTime() {
function drawTime() {"ram"
const drawSeconds = !Bangle.isLocked();
if (Bangle.isLCDOn()) {
const now = new Date();
layout.date.label = locale.time(now, BANGLEJS2 && Bangle.isLocked() ? 1 : 0); // hide seconds on bangle 2
layout.date.label = locale.time(now, !drawSeconds); // hide seconds on bangle 2
const diff = nextAlarmDate - now;
const diffHour = Math.floor((diff % 86400000) / 3600000).toString();
const diffMinutes = Math.floor(((diff % 86400000) % 3600000) / 60000).toString();
@ -101,11 +104,22 @@ function drawApp() {
layout.render();
}
setTimeout(()=>{
const period = drawSeconds ? 1000 : 60000;
if (this.drawTimeTimeout !== undefined) {
clearTimeout(this.drawTimeTimeout);
}
drawTimeTimeout = setTimeout(()=>{
drawTimeTimeout = undefined;
drawTime();
}, 1000 - (Date.now() % 1000));
}, period - (Date.now() % period));
}
Bangle.on('lock', function(on) {
if (on === false) {
drawTime();
}
});
drawTime();
}
@ -132,8 +146,9 @@ function addLog(time, type) {
var minAlarm = new Date();
var measure = true;
if (nextAlarmDate !== undefined) {
config.logs[nextAlarmDate.getDate()] = []; // overwrite log on each day of month
logs = config.logs[nextAlarmDate.getDate()];
const logday = BANGLEJS2 ? nextAlarmDate.getDate() : 0;
config.logs[logday] = []; // overwrite log on each day of month
logs = config.logs[logday];
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
@ -146,7 +161,7 @@ if (nextAlarmDate !== undefined) {
layout.render();
Bangle.setOptions({powerSave: false}); // do not dynamically change accelerometer poll interval
Bangle.setPollInterval(80); // 12.5Hz
Bangle.on('accel', (accelData) => {
Bangle.on('accel', (accelData) => {"ram"
const now = new Date();
const acc = accelData.mag;
const swest = calc_ess(acc);

View File

Before

Width:  |  Height:  |  Size: 45 KiB

After

Width:  |  Height:  |  Size: 45 KiB

View File

@ -2,7 +2,7 @@
"id": "sleepphasealarm",
"name": "SleepPhaseAlarm",
"shortName": "SleepPhaseAlarm",
"version": "0.13",
"version": "0.14",
"description": "Uses the accelerometer to estimate sleep and wake states with the principle of Estimation of Stationary Sleep-segments (ESS, see https://ubicomp.eti.uni-siegen.de/home/datasets/ichi14/index.html.en). This app will read the next alarm from the alarm application and will wake you up to 30 minutes early at the best guessed time when you are almost already awake.",
"icon": "app.png",
"tags": "alarm",
@ -15,5 +15,6 @@
{"name":"sleepphasealarm.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"sleepphasealarm.json"}],
"interface": "interface.html"
"interface": "interface.html",
"screenshots": [ {"url":"screenshot.png"}, {"url":"screenshot_log.png"} ]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -13,25 +13,102 @@
require('Storage').writeJSON(CONFIGFILE, config);
}
// Show the menu
E.showMenu({
"" : { "title" : "SleepPhaseAlarm" },
'Keep alarm enabled': {
value: !!config.settings.disableAlarm,
format: v => v?"No":"Yes",
onchange: v => {
config.settings.disableAlarm = v;
writeSettings();
function draw(log) {
const step = 10*60*1000; // resolution 10min
const yTicks = ["sleep", "awake", "alarm"];
const starttime = new Date(log[0].time);
const endtime = new Date(log[log.length-1].time);
let logidx = 0;
let curtime = starttime;
const data = new Uint8Array(Math.ceil((endtime-curtime)/step) + 1);
let curval;
let logtime;
let i=0;
while(curtime < endtime) {
if (logtime === undefined || curtime > logtime) {
curval = yTicks.indexOf(log[logidx].type);
logidx++;
logtime = new Date(log[logidx].time);
}
}, "< Back" : () => back(),
'Run before alarm': {
format: v => v === 0 ? 'disabled' : v+'h',
value: config.settings.startBeforeAlarm,
min: 0, max: 23,
onchange: v => {
config.settings.startBeforeAlarm = v;
writeSettings();
}
},
});
data[i++] = curval;
curtime = new Date(curtime + step);
}
data[i] = 1; // always end with awake
Bangle.setUI({
mode: "custom",
back: () => selectday(),
});
g.reset().setFont("6x8",1);
require("graph").drawLine(g, data, {
axes: true,
x: 4,
y: Bangle.appRect.y+8,
height: Bangle.appRect.h-20,
gridx: 1,
gridy: 1,
miny: -1,
maxy: 2,
title: /*LANG*/"Wakeup " + require("locale").date(endtime, 1),
ylabel: y => y >= 0 && y <= 1 ? yTicks[y] : "",
xlabel: x => {
if (x === Math.round(data.length/10)) {
return require("locale").time(starttime, 1);
} else if (x === (data.length-2)-Math.round(data.length/10)) {
return require("locale").time(endtime, 1);
}
return "";
},
});
}
function selectday() {
E.showMessage(/*LANG*/"Loading...");
const logs = config.logs.filter(log => log != null && log.filter(entry => entry.type === "alarm").length > 0);
logs.sort(function(a, b) { // sort by alarm date desc
const adate = new Date(a.filter(entry => entry.type === "alarm")[0].time);
const bdate = new Date(b.filter(entry => entry.type === "alarm")[0].time);
return bdate - adate;
});
const menu = {};
menu[""] = { title: /*LANG*/"Select day" };
menu["< Back"] = () => settingsmenu();
logs.forEach((log, i) => {
const date = new Date(log.filter(entry => entry.type === "alarm")[0].time);
menu[require("locale").date(date, 1)] = () => { E.showMenu(); draw(log); };
});
E.showMenu(menu);
}
function settingsmenu() {
// Show the menu
E.showMenu({
"" : { "title" : "SleepPhaseAlarm" },
'Keep alarm enabled': {
value: !!config.settings.disableAlarm,
format: v => v?"No":"Yes",
onchange: v => {
config.settings.disableAlarm = v;
writeSettings();
}
}, "< Back" : () => back(),
'Run before alarm': {
format: v => v === 0 ? 'disabled' : v+'h',
value: config.settings.startBeforeAlarm,
min: 0, max: 23,
onchange: v => {
config.settings.startBeforeAlarm = v;
writeSettings();
}
},
/*LANG*/'Select day': () => selectday(),
});
}
settingsmenu();
})

View File

@ -1,2 +1,3 @@
0.01: 3/Feb/2023 Added 'Temperature Graph' app to depository.
0.02: 4/Feb/2023 Rewrote the widget handling after discovering there's a 'widget_utils' module to properly hide and show them.
0.03: 4/Feb/2023 Fixed number error in timesData array.

View File

@ -1,395 +1,394 @@
// Temperature Graph
// BangleJS Script
Bangle.setBarometerPower(true,"tempgraph");
Bangle.loadWidgets();
var wids=WIDGETS;
var widsOn=true;
var rm=null;
var gt=null;
var dg=null;
var Layout=require("Layout");
var C=true;
var temp,tempMode,readErrCnt,watchButton2;
var graph=require("Storage").readJSON("tempgraph.json",true);
if(graph==undefined) {
graph=[];
}
var timesData=[
// dur=duration, u=time units, d=divisions on graph, s=seconds per unit.
{dur:10,u:"Mins",d:5,s:60},
{dur:20,u:"Mins",d:4,s:60},
{dur:30,u:"Mins",d:3,s:60},
{dur:40,u:"Mins",d:4,s:60},
{dur:1,u:"Hr",d:4,s:3600},
{dur:2,u:"Hrs",d:4,s:3600},
{dur:3,u:"Hrs",d:3,s:3600},
{dur:4,u:"Hrs",d:4,s:3600},
{dur:6,u:"Hrs",d:6,s:3600},
{dur:8,u:"Hrs",d:4,s:3600},
{dur:12,u:"Hrs",d:6,s:3600},
{dur:16,u:"Hrs",d:4,s:3600},
{dur:20,u:"Hrs",d:5,s:3600},
{dur:1,u:"Day",d:4,s:3600},
{dur:2,u:"Days",d:4,s:86400},
{dur:3,u:"Days",d:3,s:86400},
{dur:4,u:"Days",d:4,s:86400},
{dur:5,u:"Days",d:5,s:86400},
{dur:6,u:"Days",d:6,s:86400},
{dur:7,u:"Days",d:7,s:86400}
];
var times=[];
for(n=0;n<timesData.length;n++){
times.push(timesData[n].dur+" "+timesData[n].u);
}
var durInd=0;
var duration=times[durInd];
function drawWids(){
g.clear();
if(widsOn){
WIDGETS=wids;
Bangle.drawWidgets();
} else {
WIDGETS={};
}
}
function openMenu(){
drawWids();
E.showMenu(menu);
}
function redoMenu(){
clearInterval(rm);
E.showMenu();
openMenu();
}
function refreshMenu(){
rm = setInterval(redoMenu,100);
}
function getF(c){
// Get Fahrenheit temperature from Celsius.
return c*1.8+32;
}
function getT(){
Bangle.getPressure().then(p=>{
temp=p.temperature;
if(tempMode=="drawGraph"&&graph.length>0&&Math.abs(graph[graph.length-1].temp-temp)>10&&readErrCnt<2){
// A large change in temperature may be a reading error. ie. A 0C or less reading after
// a 20C reading. So if this happens, the reading is repeated up to 2 times to hopefully
// skip such errors.
readErrCnt++;
print("readErrCnt "+readErrCnt);
return;
}
clearInterval(gt);
readErrCnt=0;
switch (tempMode){
case "showTemp":
showT();
break;
case "drawGraph":
var date=new Date();
var dateStr=require("locale").date(date).trim();
var hrs=date.getHours();
var mins=date.getMinutes();
var secs=date.getSeconds();
graph.push({
temp:temp,
date:dateStr,
hrs:hrs,
mins:mins,
secs:secs
});
if(graph.length==1){
graph[0].dur=durInd;
}
require("Storage").writeJSON("tempgraph.json", graph);
if(graph.length==150){
clearInterval(dg);
}
drawG();
}
});
}
function getTemp(){
readErrCnt=0;
gt = setInterval(getT,800);
}
function setButton(){
var watchButton=setWatch(function(){
clearInterval(gt);
clearInterval(dg);
clearWatch(watchButton);
Bangle.removeListener("touch",screenTouch);
openMenu();
},BTN);
Bangle.on('touch',screenTouch);
}
function setButton2(){
watchButton2=setWatch(function(){
clearWatch(watchButton2);
openMenu();
},BTN);
}
function zPad(n){
return n.toString().padStart(2,0);
}
function screenTouch(n,ev){
if(ev.y>23&&ev.y<152){
C=C==false;
drawG(false);
}
}
function drawG(){
function cf(t){
if(C){
return t;
}
return getF(t);
}
drawWids();
var top=1;
var bar=21;
var barBot=175-22;
if(widsOn){
top=25;
bar=bar+24;
barBot=barBot-24;
}
var low=graph[0].temp;
var hi=low;
for(n=0;n<graph.length;n++){
var t=graph[n].temp;
if(low>t){
low=t;
}
if(hi<t){
hi=t;
}
}
var tempHi=Math.ceil((cf(hi)+2)/10)*10;
var tempLow=Math.floor((cf(low)-2)/10)*10;
var div=2;
if(tempHi-tempLow>10){
div=5;
}
if(C){
g.setColor(1,0,0);
}else{
g.setColor(0,0,1);
}
var step=(barBot-bar)/((tempHi-tempLow)/div);
for(n=0;n<graph.length;n++){
var pos=tempLow-cf(graph[n].temp);
g.drawLine(n+3,pos*(step/div)+barBot,n+3,barBot+3);
}
g.fillRect(161,barBot+5,174,barBot+20);
g.setColor(1,1,1);
g.setFont("6x8:2");
if(C){
g.drawString("C",163,barBot+5);
}else{
g.drawString("F",163,barBot+5);
}
g.setColor(0,0,0);
g.setFont6x15();
g.drawString("Temperature Graph - "+times[graph[0].dur],1,top);
g.drawRect(2,bar-4,153,barBot+4);
g.setFont("6x8:1");
var num=tempHi;
for(n=bar;n<=barBot;n=n+step){
g.drawLine(3,n,152,n);
g.drawString(num.toString().padStart(3," "),155,n-4);
num=num-div;
}
step=151/timesData[graph[0].dur].d;
for(n=step+2;n<152;n=n+step){
g.drawLine(n,bar-4,n,barBot+4);
}
grSt=graph[0];
g.drawString("Start: "+grSt.date+" "+grSt.hrs+":"+zPad(grSt.mins),1,barBot+6);
var lastT=graph[graph.length-1].temp;
g.drawString("Last Reading:",1,barBot+14);
g.setColor(1,0,0);
g.drawString(lastT.toFixed(1)+"C",85,barBot+14);
g.setColor(0,0,1);
g.drawString(getF(lastT).toFixed(1)+"F",121,barBot+14);
process.memory(true);
}
function drawGraph(){
setButton();
tempMode="drawGraph";
durInd=times.indexOf(duration);
graph=[];
getTemp();
dg=setInterval(getTemp,1000*timesData[durInd].dur*timesData[durInd].s/150);
}
function showGraph(){
setButton();
drawG();
}
function noBluetooth(){
if(NRF.getSecurityStatus().connected){
return false;
}else{
message("Error! Your\nBangle Watch\ncurrently has\nno Bluetooth\nconnection.");
return true;
}
}
function saveGraph(){
if(noBluetooth()){
return;
}
drawG();
g.flip();
g.dump();
message("Graph has\nbeen sent\nto Web IDE\nfor saving.\n");
}
function saveData(){
if(noBluetooth()){
return;
}
drawG();
g.flip();
print("Temperature Graph - "+times[graph[0].dur]+"\n");
print("\"Date\",\"Time\",\"Celsius\",\"Fahrenheit\"");
for(n=0;n<graph.length;n++){
var gr=graph[n];
print("\""+gr.date+"\",\""+gr.hrs+":"+zPad(gr.mins)+":"+zPad(gr.secs)+"\","+gr.temp+","+getF(gr.temp));
}
message("Data has\nbeen sent\nto Web IDE\nfor saving.\n");
}
function message(mes){
setButton2();
var messageLO=new Layout({
type:"v",c:[
{type:"txt",font:"6x8:2",width:171,label:mes,id:"label"},
{type:"btn",font:"6x8:2",pad:3,label:"OK",cb:l=>exit()},
],lazy:true
});
drawWids();
messageLO.render();
}
function showT(){
tempLO.lab1.label=tempLO.lab3.label;
tempLO.lab2.label=tempLO.lab4.label;
tempLO.lab3.label=tempLO.lab5.label;
tempLO.lab4.label=tempLO.lab6.label;
tempLO.lab5.label=temp.toFixed(2)+"C";
tempLO.lab6.label=getF(temp).toFixed(2)+"F";
tempLO.render();
}
function exit(){
clearWatch(watchButton2);
openMenu();
}
function showTemp(){
tempMode="showTemp";
setButton2();
tempLO=new Layout({
type:"v",c:[
{type:"h",c:[
{type:"txt",pad:5,col:"#f77",font:"6x8:2",label:" ",id:"lab1"},
{type:"txt",pad:5,col:"#77f",font:"6x8:2",label:" ",id:"lab2"}
]},
{type:"h",c:[
{type:"txt",pad:5,col:"#f77",font:"6x8:2",label:" ",id:"lab3"},
{type:"txt",pad:5,col:"#77f",font:"6x8:2",label:" ",id:"lab4"}
]},
{type:"h",c:[
{type:"txt",pad:5,col:"#f00",font:"6x8:2",label:" ",id:"lab5"},
{type:"txt",pad:5,col:"#00f",font:"6x8:2",label:" ",id:"lab6"}
]},
{type:"h",c:[
{type:"btn",pad:2,font:"6x8:2",label:"Temp",cb:l=>getTemp()},
{type:"btn",pad:2,font:"6x8:2",label:"Exit",cb:l=>exit()}
]}
]
},{lazy:true});
tempLO.render();
getTemp();
}
var menu={
"":{
"title":" Temp. Graph"
},
"Widgets":{
value:widsOn,
format:vis=>vis?"Hide":"Show",
onchange:vis=>{
widsOn=vis;
refreshMenu();
}
},
"Duration":{
value:times.indexOf(duration),
min:0,max:times.length-1,step:1,wrap:true,
format:tim=>times[tim],
onchange:(dur)=>{
duration=times[dur];
}
},
"Draw Graph":function(){
E.showMenu();
drawGraph();
},
"Show Graph" : function(){
E.showMenu();
if(graph.length>0){
showGraph();
}else{
message("No graph to\nshow as no\ngraph has been\ndrawn yet.");
}
},
"Save Graph" : function(){
E.showMenu();
if(graph.length>0){
saveGraph();
}else{
message("No graph to\nsave as no\ngraph has been\ndrawn yet.");
}
},
"Save Data" : function(){
E.showMenu();
if(graph.length>0){
saveData();
}else{
message("No data to\nsave as no\ngraph has been\ndrawn yet.");
}
},
"Show Temp":function(){
E.showMenu();
showTemp();
}
};
openMenu();
// Temperature Graph
// BangleJS Script
Bangle.setBarometerPower(true,"tempgraph");
Bangle.loadWidgets();
var widsOn=true;
var rm=null;
var gt=null;
var dg=null;
var Layout=require("Layout");
var C=true;
var temp,tempMode,readErrCnt,watchButton2;
var graph=require("Storage").readJSON("tempgraph.json",true);
if(graph==undefined) {
graph=[];
}
var timesData=[
// dur=duration, u=time units, d=divisions on graph, s=seconds per unit.
{dur:10,u:"Mins",d:5,s:60},
{dur:20,u:"Mins",d:4,s:60},
{dur:30,u:"Mins",d:3,s:60},
{dur:40,u:"Mins",d:4,s:60},
{dur:1,u:"Hr",d:4,s:3600},
{dur:2,u:"Hrs",d:4,s:3600},
{dur:3,u:"Hrs",d:3,s:3600},
{dur:4,u:"Hrs",d:4,s:3600},
{dur:6,u:"Hrs",d:6,s:3600},
{dur:8,u:"Hrs",d:4,s:3600},
{dur:12,u:"Hrs",d:6,s:3600},
{dur:16,u:"Hrs",d:4,s:3600},
{dur:20,u:"Hrs",d:5,s:3600},
{dur:1,u:"Day",d:4,s:86400},
{dur:2,u:"Days",d:4,s:86400},
{dur:3,u:"Days",d:3,s:86400},
{dur:4,u:"Days",d:4,s:86400},
{dur:5,u:"Days",d:5,s:86400},
{dur:6,u:"Days",d:6,s:86400},
{dur:7,u:"Days",d:7,s:86400}
];
var times=[];
for(n=0;n<timesData.length;n++){
times.push(timesData[n].dur+" "+timesData[n].u);
}
var durInd=0;
var duration=times[durInd];
function drawWids(){
g.clear();
if(widsOn){
Bangle.drawWidgets();
require("widget_utils").show();
} else {
require("widget_utils").hide();
}
}
function openMenu(){
drawWids();
E.showMenu(menu);
}
function redoMenu(){
clearInterval(rm);
E.showMenu();
openMenu();
}
function refreshMenu(){
rm = setInterval(redoMenu,100);
}
function getF(c){
// Get Fahrenheit temperature from Celsius.
return c*1.8+32;
}
function getT(){
Bangle.getPressure().then(p=>{
temp=p.temperature;
if(tempMode=="drawGraph"&&graph.length>0&&Math.abs(graph[graph.length-1].temp-temp)>10&&readErrCnt<2){
// A large change in temperature may be a reading error. ie. A 0C or less reading after
// a 20C reading. So if this happens, the reading is repeated up to 2 times to hopefully
// skip such errors.
readErrCnt++;
print("readErrCnt "+readErrCnt);
return;
}
clearInterval(gt);
readErrCnt=0;
switch (tempMode){
case "showTemp":
showT();
break;
case "drawGraph":
var date=new Date();
var dateStr=require("locale").date(date).trim();
var hrs=date.getHours();
var mins=date.getMinutes();
var secs=date.getSeconds();
graph.push({
temp:temp,
date:dateStr,
hrs:hrs,
mins:mins,
secs:secs
});
if(graph.length==1){
graph[0].dur=durInd;
}
require("Storage").writeJSON("tempgraph.json", graph);
if(graph.length==150){
clearInterval(dg);
}
drawG();
}
});
}
function getTemp(){
readErrCnt=0;
gt = setInterval(getT,800);
}
function setButton(){
var watchButton=setWatch(function(){
clearInterval(gt);
clearInterval(dg);
clearWatch(watchButton);
Bangle.removeListener("touch",screenTouch);
openMenu();
},BTN);
Bangle.on('touch',screenTouch);
}
function setButton2(){
watchButton2=setWatch(function(){
clearWatch(watchButton2);
openMenu();
},BTN);
}
function zPad(n){
return n.toString().padStart(2,0);
}
function screenTouch(n,ev){
if(ev.y>23&&ev.y<152){
C=C==false;
drawG(false);
}
}
function drawG(){
function cf(t){
if(C){
return t;
}
return getF(t);
}
drawWids();
var top=1;
var bar=21;
var barBot=175-22;
if(widsOn){
top=25;
bar=bar+24;
barBot=barBot-24;
}
var low=graph[0].temp;
var hi=low;
for(n=0;n<graph.length;n++){
var t=graph[n].temp;
if(low>t){
low=t;
}
if(hi<t){
hi=t;
}
}
var tempHi=Math.ceil((cf(hi)+2)/10)*10;
var tempLow=Math.floor((cf(low)-2)/10)*10;
var div=2;
if(tempHi-tempLow>10){
div=5;
}
if(C){
g.setColor(1,0,0);
}else{
g.setColor(0,0,1);
}
var step=(barBot-bar)/((tempHi-tempLow)/div);
for(n=0;n<graph.length;n++){
var pos=tempLow-cf(graph[n].temp);
g.drawLine(n+3,pos*(step/div)+barBot,n+3,barBot+3);
}
g.fillRect(161,barBot+5,174,barBot+20);
g.setColor(1,1,1);
g.setFont("6x8:2");
if(C){
g.drawString("C",163,barBot+5);
}else{
g.drawString("F",163,barBot+5);
}
g.setColor(0,0,0);
g.setFont6x15();
g.drawString("Temperature Graph - "+times[graph[0].dur],1,top);
g.drawRect(2,bar-4,153,barBot+4);
g.setFont("6x8:1");
var num=tempHi;
for(n=bar;n<=barBot;n=n+step){
g.drawLine(3,n,152,n);
g.drawString(num.toString().padStart(3," "),155,n-4);
num=num-div;
}
step=151/timesData[graph[0].dur].d;
for(n=step+2;n<152;n=n+step){
g.drawLine(n,bar-4,n,barBot+4);
}
grSt=graph[0];
g.drawString("Start: "+grSt.date+" "+grSt.hrs+":"+zPad(grSt.mins),1,barBot+6);
var lastT=graph[graph.length-1].temp;
g.drawString("Last Reading:",1,barBot+14);
g.setColor(1,0,0);
g.drawString(lastT.toFixed(1)+"C",85,barBot+14);
g.setColor(0,0,1);
g.drawString(getF(lastT).toFixed(1)+"F",121,barBot+14);
process.memory(true);
}
function drawGraph(){
setButton();
tempMode="drawGraph";
durInd=times.indexOf(duration);
graph=[];
getTemp();
dg=setInterval(getTemp,1000*timesData[durInd].dur*timesData[durInd].s/150);
}
function showGraph(){
setButton();
drawG();
}
function noBluetooth(){
if(NRF.getSecurityStatus().connected){
return false;
}else{
message("Error! Your\nBangle Watch\ncurrently has\nno Bluetooth\nconnection.");
return true;
}
}
function saveGraph(){
if(noBluetooth()){
return;
}
drawG();
g.flip();
g.dump();
message("Graph has\nbeen sent\nto Web IDE\nfor saving.\n");
}
function saveData(){
if(noBluetooth()){
return;
}
drawG();
g.flip();
print("Temperature Graph - "+times[graph[0].dur]+"\n");
print("\"Date\",\"Time\",\"Celsius\",\"Fahrenheit\"");
for(n=0;n<graph.length;n++){
var gr=graph[n];
print("\""+gr.date+"\",\""+gr.hrs+":"+zPad(gr.mins)+":"+zPad(gr.secs)+"\","+gr.temp+","+getF(gr.temp));
}
message("Data has\nbeen sent\nto Web IDE\nfor saving.\n");
}
function message(mes){
setButton2();
var messageLO=new Layout({
type:"v",c:[
{type:"txt",font:"6x8:2",width:171,label:mes,id:"label"},
{type:"btn",font:"6x8:2",pad:3,label:"OK",cb:l=>exit()},
],lazy:true
});
drawWids();
messageLO.render();
}
function showT(){
tempLO.lab1.label=tempLO.lab3.label;
tempLO.lab2.label=tempLO.lab4.label;
tempLO.lab3.label=tempLO.lab5.label;
tempLO.lab4.label=tempLO.lab6.label;
tempLO.lab5.label=temp.toFixed(2)+"C";
tempLO.lab6.label=getF(temp).toFixed(2)+"F";
tempLO.render();
}
function exit(){
clearWatch(watchButton2);
openMenu();
}
function showTemp(){
tempMode="showTemp";
setButton2();
tempLO=new Layout({
type:"v",c:[
{type:"h",c:[
{type:"txt",pad:5,col:"#f77",font:"6x8:2",label:" ",id:"lab1"},
{type:"txt",pad:5,col:"#77f",font:"6x8:2",label:" ",id:"lab2"}
]},
{type:"h",c:[
{type:"txt",pad:5,col:"#f77",font:"6x8:2",label:" ",id:"lab3"},
{type:"txt",pad:5,col:"#77f",font:"6x8:2",label:" ",id:"lab4"}
]},
{type:"h",c:[
{type:"txt",pad:5,col:"#f00",font:"6x8:2",label:" ",id:"lab5"},
{type:"txt",pad:5,col:"#00f",font:"6x8:2",label:" ",id:"lab6"}
]},
{type:"h",c:[
{type:"btn",pad:2,font:"6x8:2",label:"Temp",cb:l=>getTemp()},
{type:"btn",pad:2,font:"6x8:2",label:"Exit",cb:l=>exit()}
]}
]
},{lazy:true});
tempLO.render();
getTemp();
}
var menu={
"":{
"title":" Temp. Graph"
},
"Widgets":{
value:widsOn,
format:vis=>vis?"Hide":"Show",
onchange:vis=>{
widsOn=vis;
refreshMenu();
}
},
"Duration":{
value:times.indexOf(duration),
min:0,max:times.length-1,step:1,wrap:true,
format:tim=>times[tim],
onchange:(dur)=>{
duration=times[dur];
}
},
"Draw Graph":function(){
E.showMenu();
drawGraph();
},
"Show Graph" : function(){
E.showMenu();
if(graph.length>0){
showGraph();
}else{
message("No graph to\nshow as no\ngraph has been\ndrawn yet.");
}
},
"Save Graph" : function(){
E.showMenu();
if(graph.length>0){
saveGraph();
}else{
message("No graph to\nsave as no\ngraph has been\ndrawn yet.");
}
},
"Save Data" : function(){
E.showMenu();
if(graph.length>0){
saveData();
}else{
message("No data to\nsave as no\ngraph has been\ndrawn yet.");
}
},
"Show Temp":function(){
E.showMenu();
showTemp();
}
};
openMenu();

View File

@ -1,7 +1,7 @@
{ "id": "tempgraph",
"name": "Temperature Graph",
"shortName":"Temp Graph",
"version":"0.01",
"version":"0.03",
"description": "An app for recording the temperature for time periods ranging from 10 minutes to 7 days.",
"icon": "app.png",
"type": "app",

View File

@ -6,3 +6,4 @@
0.07: Move CHARGING variable to more readable string
0.08: Ensure battery updates every 60s even if LCD was on at boot and stays on
0.09: Misc speed/memory tweaks
0.10: Color changes due to the battery level

View File

@ -1,7 +1,7 @@
{
"id": "widbat",
"name": "Battery Level Widget",
"version": "0.09",
"version": "0.10",
"description": "Show the current battery level and charging status in the top right of the clock",
"icon": "widget.png",
"type": "widget",

View File

@ -31,7 +31,11 @@
x+=16;
}
g.setColor(g.theme.fg).fillRect(x,y+2,x+s-4,y+21).clearRect(x+2,y+4,x+s-6,y+19).fillRect(x+s-3,y+10,x+s,y+14);
g.setColor("#0f0").fillRect(x+4,y+6,x+4+E.getBattery()*(s-12)/100,y+17);
var battery = E.getBattery();
if(battery < 20) {g.setColor("#f00");}
else if (battery < 50) {g.setColor("#ff0");}
else {g.setColor("#0f0");}
g.fillRect(x+4,y+6,x+4+battery*(s-12)/100,y+17);
}};
setWidth();
})()