|
@ -6,5 +6,7 @@ Changed for individual apps are listed in `apps/appname/ChangeLog`
|
||||||
* `Remove All Apps` now doesn't perform a reset before erase - fixes inability to update firmware if settings are wrong
|
* `Remove All Apps` now doesn't perform a reset before erase - fixes inability to update firmware if settings are wrong
|
||||||
* Added optional `README.md` file for apps
|
* Added optional `README.md` file for apps
|
||||||
* Remove 2v04 version warning, add links in About to official/developer versions
|
* Remove 2v04 version warning, add links in About to official/developer versions
|
||||||
* Fix issue removing an app that was just installed (Fix #253)
|
* Fix issue removing an app that was just installed (fix #253)
|
||||||
* Add `Favourite` functionality
|
* Add `Favourite` functionality
|
||||||
|
* Version number now clickable even when you're at the latest version (fix #291)
|
||||||
|
* Rewrite 'getInstalledApps' to minimize RAM usage
|
||||||
|
|
95
apps.json
|
@ -78,7 +78,7 @@
|
||||||
{ "id": "welcome",
|
{ "id": "welcome",
|
||||||
"name": "Welcome",
|
"name": "Welcome",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.06",
|
"version":"0.07",
|
||||||
"description": "Appears at first boot and explains how to use Bangle.js",
|
"description": "Appears at first boot and explains how to use Bangle.js",
|
||||||
"tags": "start,welcome",
|
"tags": "start,welcome",
|
||||||
"allow_emulator":true,
|
"allow_emulator":true,
|
||||||
|
@ -86,13 +86,14 @@
|
||||||
{"name":"welcome.boot.js","url":"boot.js"},
|
{"name":"welcome.boot.js","url":"boot.js"},
|
||||||
{"name":"welcome.app.js","url":"app.js"},
|
{"name":"welcome.app.js","url":"app.js"},
|
||||||
{"name":"welcome.settings.js","url":"settings.js"},
|
{"name":"welcome.settings.js","url":"settings.js"},
|
||||||
|
{"name":"welcome.settings.json","url":"settings-default.json","evaluate":true},
|
||||||
{"name":"welcome.img","url":"app-icon.js","evaluate":true}
|
{"name":"welcome.img","url":"app-icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{ "id": "gbridge",
|
{ "id": "gbridge",
|
||||||
"name": "Gadgetbridge",
|
"name": "Gadgetbridge",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.08",
|
"version":"0.09",
|
||||||
"description": "The default notification handler for Gadgetbridge notifications from Android",
|
"description": "The default notification handler for Gadgetbridge notifications from Android",
|
||||||
"tags": "tool,system,android,widget",
|
"tags": "tool,system,android,widget",
|
||||||
"type":"widget",
|
"type":"widget",
|
||||||
|
@ -119,7 +120,7 @@
|
||||||
{ "id": "setting",
|
{ "id": "setting",
|
||||||
"name": "Settings",
|
"name": "Settings",
|
||||||
"icon": "settings.png",
|
"icon": "settings.png",
|
||||||
"version":"0.13",
|
"version":"0.15",
|
||||||
"description": "A menu for setting up Bangle.js",
|
"description": "A menu for setting up Bangle.js",
|
||||||
"tags": "tool,system",
|
"tags": "tool,system",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
@ -279,7 +280,7 @@
|
||||||
{ "id": "gpsrec",
|
{ "id": "gpsrec",
|
||||||
"name": "GPS Recorder",
|
"name": "GPS Recorder",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.06",
|
"version":"0.07",
|
||||||
"interface": "interface.html",
|
"interface": "interface.html",
|
||||||
"description": "Application that allows you to record a GPS track. Can run in background",
|
"description": "Application that allows you to record a GPS track. Can run in background",
|
||||||
"tags": "tool,outdoors,gps,widget",
|
"tags": "tool,outdoors,gps,widget",
|
||||||
|
@ -329,7 +330,7 @@
|
||||||
{ "id": "widbat",
|
{ "id": "widbat",
|
||||||
"name": "Battery Level Widget",
|
"name": "Battery Level Widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"version":"0.04",
|
"version":"0.05",
|
||||||
"description": "Show the current battery level and charging status in the top right of the clock",
|
"description": "Show the current battery level and charging status in the top right of the clock",
|
||||||
"tags": "widget,battery",
|
"tags": "widget,battery",
|
||||||
"type":"widget",
|
"type":"widget",
|
||||||
|
@ -341,7 +342,7 @@
|
||||||
"name": "Battery Level Widget (with percentage)",
|
"name": "Battery Level Widget (with percentage)",
|
||||||
"shortName": "Battery Widget",
|
"shortName": "Battery Widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"version":"0.08",
|
"version":"0.09",
|
||||||
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
|
"description": "Show the current battery level and charging status in the top right of the clock, with charge percentage",
|
||||||
"tags": "widget,battery",
|
"tags": "widget,battery",
|
||||||
"type":"widget",
|
"type":"widget",
|
||||||
|
@ -354,7 +355,7 @@
|
||||||
{ "id": "widbt",
|
{ "id": "widbt",
|
||||||
"name": "Bluetooth Widget",
|
"name": "Bluetooth Widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"description": "Show the current Bluetooth connection status in the top right of the clock",
|
"description": "Show the current Bluetooth connection status in the top right of the clock",
|
||||||
"tags": "widget,bluetooth",
|
"tags": "widget,bluetooth",
|
||||||
"type":"widget",
|
"type":"widget",
|
||||||
|
@ -362,6 +363,18 @@
|
||||||
{"name":"widbt.wid.js","url":"widget.js"}
|
{"name":"widbt.wid.js","url":"widget.js"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{ "id": "widram",
|
||||||
|
"name": "RAM Widget",
|
||||||
|
"shortName":"RAM Widget",
|
||||||
|
"icon": "widget.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Display your Bangle's available RAM percentage in a widget",
|
||||||
|
"tags": "widget",
|
||||||
|
"type": "widget",
|
||||||
|
"storage": [
|
||||||
|
{"name":"widram.wid.js","url":"widget.js"}
|
||||||
|
]
|
||||||
|
},
|
||||||
{ "id": "hrm",
|
{ "id": "hrm",
|
||||||
"name": "Heart Rate Monitor",
|
"name": "Heart Rate Monitor",
|
||||||
"icon": "heartrate.png",
|
"icon": "heartrate.png",
|
||||||
|
@ -504,13 +517,14 @@
|
||||||
"id": "ncstart",
|
"id": "ncstart",
|
||||||
"name": "NCEU Startup",
|
"name": "NCEU Startup",
|
||||||
"icon": "start.png",
|
"icon": "start.png",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"description": "NodeConfEU 2019 'First Start' Sequence",
|
"description": "NodeConfEU 2019 'First Start' Sequence",
|
||||||
"tags": "start,welcome",
|
"tags": "start,welcome",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"ncstart.app.js","url":"start.js"},
|
{"name":"ncstart.app.js","url":"start.js"},
|
||||||
{"name":"ncstart.boot.js","url":"boot.js"},
|
{"name":"ncstart.boot.js","url":"boot.js"},
|
||||||
{"name":"ncstart.settings.js","url":"settings.js"},
|
{"name":"ncstart.settings.js","url":"settings.js"},
|
||||||
|
{"name":"ncstart.settings.json","url":"settings-default.json","evaluate":true},
|
||||||
{"name":"ncstart.img","url":"start-icon.js","evaluate":true},
|
{"name":"ncstart.img","url":"start-icon.js","evaluate":true},
|
||||||
{"name":"nc-bangle.img","url":"start-bangle.js","evaluate":true},
|
{"name":"nc-bangle.img","url":"start-bangle.js","evaluate":true},
|
||||||
{"name":"nc-nceu.img","url":"start-nceu.js","evaluate":true},
|
{"name":"nc-nceu.img","url":"start-nceu.js","evaluate":true},
|
||||||
|
@ -775,7 +789,7 @@
|
||||||
{ "id": "widclk",
|
{ "id": "widclk",
|
||||||
"name": "Digital clock widget",
|
"name": "Digital clock widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
"version":"0.03",
|
"version":"0.04",
|
||||||
"description": "A simple digital clock widget",
|
"description": "A simple digital clock widget",
|
||||||
"tags": "widget,clock",
|
"tags": "widget,clock",
|
||||||
"type":"widget",
|
"type":"widget",
|
||||||
|
@ -915,7 +929,7 @@
|
||||||
{ "id": "marioclock",
|
{ "id": "marioclock",
|
||||||
"name": "Mario Clock",
|
"name": "Mario Clock",
|
||||||
"icon": "marioclock.png",
|
"icon": "marioclock.png",
|
||||||
"version":"0.09",
|
"version":"0.12",
|
||||||
"description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.",
|
"description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.",
|
||||||
"tags": "clock,mario,retro",
|
"tags": "clock,mario,retro",
|
||||||
"type": "clock",
|
"type": "clock",
|
||||||
|
@ -1108,6 +1122,20 @@
|
||||||
{"name":"openstmap.app.js","url":"app.js"},
|
{"name":"openstmap.app.js","url":"app.js"},
|
||||||
{"name":"openstmap.img","url":"app-icon.js","evaluate":true}
|
{"name":"openstmap.img","url":"app-icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{ "id": "activepedom",
|
||||||
|
"name": "Active Pedometer",
|
||||||
|
"shortName":"Active Pedometer",
|
||||||
|
"icon": "app.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Pedometer that filters out arm movement and displays a step goal progress.",
|
||||||
|
"tags": "outdoors,widget",
|
||||||
|
"type":"widget",
|
||||||
|
"storage": [
|
||||||
|
{"name":"activepedom.wid.js","url":"widget.js"},
|
||||||
|
{"name":"activepedom.settings.js","url":"settings.js"},
|
||||||
|
{"name":"activepedom.img","url":"app-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{ "id": "tabata",
|
{ "id": "tabata",
|
||||||
"name": "Tabata",
|
"name": "Tabata",
|
||||||
|
@ -1148,9 +1176,9 @@
|
||||||
},
|
},
|
||||||
{ "id": "batchart",
|
{ "id": "batchart",
|
||||||
"name": "Battery Chart",
|
"name": "Battery Chart",
|
||||||
"shortName":"BatChart",
|
"shortName":"Battery Chart",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"version":"0.03",
|
"version":"0.07",
|
||||||
"description": "A widget and an app for recording and visualizing battery percentage over time.",
|
"description": "A widget and an app for recording and visualizing battery percentage over time.",
|
||||||
"tags": "app,widget,battery,time,record,chart,tool",
|
"tags": "app,widget,battery,time,record,chart,tool",
|
||||||
"storage": [
|
"storage": [
|
||||||
|
@ -1159,11 +1187,25 @@
|
||||||
{"name":"batchart.img","url":"app-icon.js","evaluate":true}
|
{"name":"batchart.img","url":"app-icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{ "id": "nato",
|
||||||
|
"name": "NATO Alphabet",
|
||||||
|
"shortName" : "NATOAlphabet",
|
||||||
|
"icon": "nato.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"type": "app",
|
||||||
|
"description": "Learn the NATO Phonetic alphabet plus some numbers.",
|
||||||
|
"tags": "app,learn,visual",
|
||||||
|
"allow_emulator":true,
|
||||||
|
"storage": [
|
||||||
|
{"name":"nato.app.js","url":"nato.js"},
|
||||||
|
{"name":"nato.img","url":"nato-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
},
|
||||||
{ "id": "numerals",
|
{ "id": "numerals",
|
||||||
"name": "Numerals Clock",
|
"name": "Numerals Clock",
|
||||||
"shortName": "Numerals Clock",
|
"shortName": "Numerals Clock",
|
||||||
"icon": "numerals.png",
|
"icon": "numerals.png",
|
||||||
"version":"0.01",
|
"version":"0.02",
|
||||||
"description": "A simple big numerals clock",
|
"description": "A simple big numerals clock",
|
||||||
"tags": "numerals,clock",
|
"tags": "numerals,clock",
|
||||||
"type":"clock",
|
"type":"clock",
|
||||||
|
@ -1181,10 +1223,35 @@
|
||||||
"version":"0.02",
|
"version":"0.02",
|
||||||
"description": "Detect BLE devices and show some informations.",
|
"description": "Detect BLE devices and show some informations.",
|
||||||
"tags": "app,bluetooth,tool",
|
"tags": "app,bluetooth,tool",
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"bledetect.app.js","url":"bledetect.js"},
|
{"name":"bledetect.app.js","url":"bledetect.js"},
|
||||||
{"name":"bledetect.img","url":"bledetect-icon.js","evaluate":true}
|
{"name":"bledetect.img","url":"bledetect-icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{ "id": "snake",
|
||||||
|
"name": "Snake",
|
||||||
|
"shortName":"Snake",
|
||||||
|
"icon": "snake.png",
|
||||||
|
"version":"0.02",
|
||||||
|
"description": "The classic snake game. Eat apples and don't bite your tail.",
|
||||||
|
"tags": "game,fun",
|
||||||
|
"readme": "README.md",
|
||||||
|
"storage": [
|
||||||
|
{"name":"snake.app.js","url":"snake.js"},
|
||||||
|
{"name":"snake.img","url":"snake-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ "id": "calculator",
|
||||||
|
"name": "Calculator",
|
||||||
|
"shortName":"Calculator",
|
||||||
|
"icon": "calculator.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Basic calculator reminiscent of MacOs's one. Handy for small calculus. Push button1 and 3 to navigate up/down, tap right or left to navigate the sides, push button 2 to select.",
|
||||||
|
"tags": "app,tool",
|
||||||
|
"storage": [
|
||||||
|
{"name":"calculator.app.js","url":"app.js"},
|
||||||
|
{"name":"calculator.img","url":"calculator-icon.js","evaluate":true}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
After Width: | Height: | Size: 370 B |
After Width: | Height: | Size: 374 B |
After Width: | Height: | Size: 338 B |
|
@ -0,0 +1 @@
|
||||||
|
0.01: New Widget!
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Improved pedometer
|
||||||
|
Pedometer that filters out arm movement and displays a step goal progress.
|
||||||
|
|
||||||
|
I changed the step counting algorithm completely.
|
||||||
|
Now every step is counted when in status 'active', if the time difference between two steps is not too short or too long.
|
||||||
|
To get in 'active' mode, you have to reach the step threshold before the active timer runs out.
|
||||||
|
When you reach the step threshold, the steps needed to reach the threshold are counted as well.
|
||||||
|
|
||||||
|
## Screenshots
|
||||||
|
* 600 steps
|
||||||
|

|
||||||
|
|
||||||
|
* 1600 steps
|
||||||
|

|
||||||
|
|
||||||
|
* 10600 steps
|
||||||
|

|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Two line display
|
||||||
|
* Large number for good readability
|
||||||
|
* Small number with the exact steps counted
|
||||||
|
* Large number is displayed in green when status is 'active'
|
||||||
|
* Progress bar for step goal
|
||||||
|
* Counts steps only if they are reached in a certain time
|
||||||
|
* Filters out steps where time between two steps is too long or too short
|
||||||
|
* Step detection sensitivity from firmware can be configured
|
||||||
|
* Steps are saved to a file and read-in at start (to not lose step progress)
|
||||||
|
* Settings can be changed in Settings - App/widget settings - Active Pedometer
|
||||||
|
|
||||||
|
## Development version
|
||||||
|
|
||||||
|
* https://github.com/Purple-Tentacle/BangleAppsDev/tree/master/apps/pedometer
|
||||||
|
|
||||||
|
## Requests
|
||||||
|
|
||||||
|
If you have any feature requests, please post in this forum thread: http://forum.espruino.com/conversations/345754/
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwIGDvAEDgP+ApMD/4FVEZY1FABcP8AFDn/wAod/AocB//4AoUHAokPAokf5/8AocfAoc+j5HDvgFEvEf7+AAoP4AoJCC+E/54qCsE/wYkDn+AAos8AohZDj/AAohrEp4FEs5xEuJfDgF5Aon4GgYFBGgZOBnyJD+EeYgfgj4FEh6VD4AFDh+AAIJMCBoIFFLQQtBgYFCHIIFDjA3BC4I="))
|
After Width: | Height: | Size: 836 B |
|
@ -0,0 +1,81 @@
|
||||||
|
// This file should contain exactly one function, which shows the app's settings
|
||||||
|
/**
|
||||||
|
* @param {function} back Use back() to return to settings menu
|
||||||
|
*/
|
||||||
|
(function(back) {
|
||||||
|
const SETTINGS_FILE = 'activepedom.settings.json';
|
||||||
|
|
||||||
|
// initialize with default settings...
|
||||||
|
let s = {
|
||||||
|
'cMaxTime' : 1100,
|
||||||
|
'cMinTime' : 240,
|
||||||
|
'stepThreshold' : 30,
|
||||||
|
'intervalResetActive' : 30000,
|
||||||
|
'stepSensitivity' : 80,
|
||||||
|
'stepGoal' : 10000,
|
||||||
|
};
|
||||||
|
// ...and overwrite them with any saved values
|
||||||
|
// This way saved values are preserved if a new version adds more settings
|
||||||
|
const storage = require('Storage');
|
||||||
|
const saved = storage.readJSON(SETTINGS_FILE, 1) || {};
|
||||||
|
for (const key in saved) {
|
||||||
|
s[key] = saved[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a function to safe a specific setting, e.g. save('color')(1)
|
||||||
|
function save(key) {
|
||||||
|
return function (value) {
|
||||||
|
s[key] = value;
|
||||||
|
storage.write(SETTINGS_FILE, s);
|
||||||
|
WIDGETS["activepedom"].draw();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const menu = {
|
||||||
|
'': { 'title': 'Active Pedometer' },
|
||||||
|
'< Back': back,
|
||||||
|
'Max time (ms)': {
|
||||||
|
value: s.cMaxTime,
|
||||||
|
min: 0,
|
||||||
|
max: 10000,
|
||||||
|
step: 100,
|
||||||
|
onchange: save('cMaxTime'),
|
||||||
|
},
|
||||||
|
'Min time (ms)': {
|
||||||
|
value: s.cMinTime,
|
||||||
|
min: 0,
|
||||||
|
max: 500,
|
||||||
|
step: 10,
|
||||||
|
onchange: save('cMinTime'),
|
||||||
|
},
|
||||||
|
'Step threshold': {
|
||||||
|
value: s.stepThreshold,
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
step: 1,
|
||||||
|
onchange: save('stepThreshold'),
|
||||||
|
},
|
||||||
|
'Act.Res. (ms)': {
|
||||||
|
value: s.intervalResetActive,
|
||||||
|
min: 100,
|
||||||
|
max: 100000,
|
||||||
|
step: 1000,
|
||||||
|
onchange: save('intervalResetActive'),
|
||||||
|
},
|
||||||
|
'Step sens.': {
|
||||||
|
value: s.stepSensitivity,
|
||||||
|
min: 0,
|
||||||
|
max: 1000,
|
||||||
|
step: 10,
|
||||||
|
onchange: save('stepSensitivity'),
|
||||||
|
},
|
||||||
|
'Step goal': {
|
||||||
|
value: s.stepGoal,
|
||||||
|
min: 1000,
|
||||||
|
max: 100000,
|
||||||
|
step: 1000,
|
||||||
|
onchange: save('stepGoal'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
E.showMenu(menu);
|
||||||
|
});
|
|
@ -0,0 +1,180 @@
|
||||||
|
(() => {
|
||||||
|
var stepTimeDiff = 9999; //Time difference between two steps
|
||||||
|
var startTimeStep = new Date(); //set start time
|
||||||
|
var stopTimeStep = 0; //Time after one step
|
||||||
|
var timerResetActive = 0; //timer to reset active
|
||||||
|
var steps = 0; //steps taken
|
||||||
|
var stepsCounted = 0; //active steps counted
|
||||||
|
var active = 0; //x steps in y seconds achieved
|
||||||
|
var stepGoalPercent = 0; //percentage of step goal
|
||||||
|
var stepGoalBarLength = 0; //length og progress bar
|
||||||
|
var lastUpdate = new Date();
|
||||||
|
var width = 45;
|
||||||
|
|
||||||
|
var stepsTooShort = 0;
|
||||||
|
var stepsTooLong = 0;
|
||||||
|
var stepsOutsideTime = 0;
|
||||||
|
|
||||||
|
//define default settings
|
||||||
|
const DEFAULTS = {
|
||||||
|
'cMaxTime' : 1100,
|
||||||
|
'cMinTime' : 240,
|
||||||
|
'stepThreshold' : 30,
|
||||||
|
'intervalResetActive' : 30000,
|
||||||
|
'stepSensitivity' : 80,
|
||||||
|
'stepGoal' : 10000,
|
||||||
|
};
|
||||||
|
const SETTINGS_FILE = 'activepedom.settings.json';
|
||||||
|
const PEDOMFILE = "activepedom.steps.json";
|
||||||
|
|
||||||
|
let settings;
|
||||||
|
//load settings
|
||||||
|
function loadSettings() {
|
||||||
|
settings = require('Storage').readJSON(SETTINGS_FILE, 1) || {};
|
||||||
|
}
|
||||||
|
//return setting
|
||||||
|
function setting(key) {
|
||||||
|
if (!settings) { loadSettings(); }
|
||||||
|
return (key in settings) ? settings[key] : DEFAULTS[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setStepSensitivity(s) {
|
||||||
|
function sqr(x) { return x*x; }
|
||||||
|
var X=sqr(8192-s);
|
||||||
|
var Y=sqr(8192+s);
|
||||||
|
Bangle.setOptions({stepCounterThresholdLow:X,stepCounterThresholdHigh:Y});
|
||||||
|
}
|
||||||
|
|
||||||
|
//format number to make them shorter
|
||||||
|
function kFormatter(num) {
|
||||||
|
if (num <= 999) return num; //smaller 1.000, return 600 as 600
|
||||||
|
if (num >= 1000 && num < 10000) { //between 1.000 and 10.000
|
||||||
|
num = Math.floor(num/100)*100;
|
||||||
|
return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; //return 1600 as 1.6k
|
||||||
|
}
|
||||||
|
if (num >= 10000) { //greater 10.000
|
||||||
|
num = Math.floor(num/1000)*1000;
|
||||||
|
return (num / 1000).toFixed(1).replace(/\.0$/, '') + 'k'; //return 10.600 as 10k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Set Active to 0
|
||||||
|
function resetActive() {
|
||||||
|
active = 0;
|
||||||
|
steps = 0;
|
||||||
|
if (Bangle.isLCDOn()) WIDGETS["activepedom"].draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
function calcSteps() {
|
||||||
|
stopTimeStep = new Date(); //stop time after each step
|
||||||
|
stepTimeDiff = stopTimeStep - startTimeStep; //time between steps in milliseconds
|
||||||
|
startTimeStep = new Date(); //start time again
|
||||||
|
|
||||||
|
//Remove step if time between first and second step is too long
|
||||||
|
if (stepTimeDiff >= setting('cMaxTime')) { //milliseconds
|
||||||
|
stepsTooLong++; //count steps which are note counted, because time too long
|
||||||
|
steps--;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Remove step if time between first and second step is too short
|
||||||
|
if (stepTimeDiff <= setting('cMinTime')) { //milliseconds
|
||||||
|
stepsTooShort++; //count steps which are note counted, because time too short
|
||||||
|
steps--;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (steps >= setting('stepThreshold')) {
|
||||||
|
if (active == 0) {
|
||||||
|
stepsCounted = stepsCounted + (setting('stepThreshold') -1) ; //count steps needed to reach active status, last step is counted anyway, so treshold -1
|
||||||
|
stepsOutsideTime = stepsOutsideTime - 10; //substract steps needed to reac active status
|
||||||
|
}
|
||||||
|
active = 1;
|
||||||
|
clearInterval(timerResetActive); //stop timer which resets active
|
||||||
|
timerResetActive = setInterval(resetActive, setting('intervalResetActive')); //reset active after timer runs out
|
||||||
|
steps = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active == 1) {
|
||||||
|
stepsCounted++; //count steps
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stepsOutsideTime++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
var height = 23; //width is deined globally
|
||||||
|
var stepsDisplayLarge = kFormatter(stepsCounted);
|
||||||
|
|
||||||
|
//Check if same day
|
||||||
|
let date = new Date();
|
||||||
|
if (lastUpdate.getDate() == date.getDate()){ //if same day
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
stepsCounted = 1; //set stepcount to 1
|
||||||
|
}
|
||||||
|
lastUpdate = date;
|
||||||
|
|
||||||
|
g.reset();
|
||||||
|
g.clearRect(this.x, this.y, this.x+width, this.y+height);
|
||||||
|
|
||||||
|
//draw numbers
|
||||||
|
if (active == 1) g.setColor(0x07E0); //green
|
||||||
|
else g.setColor(0xFFFF); //white
|
||||||
|
g.setFont("6x8", 2);
|
||||||
|
g.drawString(stepsDisplayLarge,this.x+1,this.y); //first line, big number
|
||||||
|
g.setFont("6x8", 1);
|
||||||
|
g.setColor(0xFFFF); //white
|
||||||
|
g.drawString(stepsCounted,this.x+1,this.y+14); //second line, small number
|
||||||
|
|
||||||
|
//draw step goal bar
|
||||||
|
stepGoalPercent = (stepsCounted / setting('stepGoal')) * 100;
|
||||||
|
stepGoalBarLength = width / 100 * stepGoalPercent;
|
||||||
|
if (stepGoalBarLength > width) stepGoalBarLength = width; //do not draw across width of widget
|
||||||
|
g.setColor(0x7BEF); //grey
|
||||||
|
g.fillRect(this.x, this.y+height, this.x+width, this.y+height); // draw background bar
|
||||||
|
g.setColor(0xFFFF); //white
|
||||||
|
g.fillRect(this.x, this.y+height, this.x+1, this.y+height-1); //draw start of bar
|
||||||
|
g.fillRect(this.x+width, this.y+height, this.x+width-1, this.y+height-1); //draw end of bar
|
||||||
|
g.fillRect(this.x, this.y+height, this.x+stepGoalBarLength, this.y+height); // draw progress bar
|
||||||
|
}
|
||||||
|
|
||||||
|
//This event is called just before the device shuts down for commands such as reset(), load(), save(), E.reboot() or Bangle.off()
|
||||||
|
E.on('kill', () => {
|
||||||
|
let d = { //define array to write to file
|
||||||
|
lastUpdate : lastUpdate.toISOString(),
|
||||||
|
stepsToday : stepsCounted,
|
||||||
|
stepsTooShort : stepsTooShort,
|
||||||
|
stepsTooLong : stepsTooLong,
|
||||||
|
stepsOutsideTime : stepsOutsideTime
|
||||||
|
};
|
||||||
|
require("Storage").write(PEDOMFILE,d); //write array to file
|
||||||
|
});
|
||||||
|
|
||||||
|
//When Step is registered by firmware
|
||||||
|
Bangle.on('step', (up) => {
|
||||||
|
steps++; //increase step count
|
||||||
|
calcSteps();
|
||||||
|
if (Bangle.isLCDOn()) WIDGETS["activepedom"].draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
// redraw when the LCD turns on
|
||||||
|
Bangle.on('lcdPower', function(on) {
|
||||||
|
if (on) WIDGETS["activepedom"].draw();
|
||||||
|
});
|
||||||
|
|
||||||
|
//Read data from file and set variables
|
||||||
|
let pedomData = require("Storage").readJSON(PEDOMFILE,1);
|
||||||
|
if (pedomData) {
|
||||||
|
if (pedomData.lastUpdate) lastUpdate = new Date(pedomData.lastUpdate);
|
||||||
|
stepsCounted = pedomData.stepsToday|0;
|
||||||
|
stepsTooShort = pedomData.stepsTooShort;
|
||||||
|
stepsTooLong = pedomData.stepsTooLong;
|
||||||
|
stepsOutsideTime = pedomData.stepsOutsideTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
setStepSensitivity(setting('stepSensitivity')); //set step sensitivity (80 is standard, 400 is muss less sensitive)
|
||||||
|
|
||||||
|
//Add widget
|
||||||
|
WIDGETS["activepedom"]={area:"tl",width:width,draw:draw};
|
||||||
|
|
||||||
|
})();
|
|
@ -1,3 +1,7 @@
|
||||||
0.01: New app and widget
|
0.01: New app and widget
|
||||||
0.02: Widget stores data to file (1 dataset/10min)
|
0.02: Widget stores data to file (1 dataset/10min)
|
||||||
0.03: Rotate log files once a week.
|
0.03: Rotate log files once a week.
|
||||||
|
0.04: chart in the app is now active.
|
||||||
|
0.05: Display temperature and LCD state in chart
|
||||||
|
0.06: Fixes widget events and charting of component states
|
||||||
|
0.07: Improve logging and charting of component states and add widget icon
|
|
@ -1,20 +1,213 @@
|
||||||
// place your const, vars, functions or classes here
|
const GraphXZero = 40;
|
||||||
|
const GraphYZero = 180;
|
||||||
|
const GraphY100 = 80;
|
||||||
|
|
||||||
function renderBatteryChart(){
|
const GraphMarkerOffset = 5;
|
||||||
g.drawString("t", 215, 175);
|
const MaxValueCount = 144;
|
||||||
g.drawLine(40,190,40,80);
|
const GraphXMax = GraphXZero + MaxValueCount;
|
||||||
|
|
||||||
|
const GraphLcdY = GraphYZero + 10;
|
||||||
|
const GraphCompassY = GraphYZero + 16;
|
||||||
|
// const GraphBluetoothY = GraphYZero + 22;
|
||||||
|
const GraphGpsY = GraphYZero + 28;
|
||||||
|
const GraphHrmY = GraphYZero + 34;
|
||||||
|
|
||||||
|
const Storage = require("Storage");
|
||||||
|
|
||||||
|
function renderCoordinateSystem() {
|
||||||
|
g.setFont("6x8", 1);
|
||||||
|
|
||||||
g.drawString("%", 39, 70);
|
// Left Y axis (Battery)
|
||||||
g.drawString("100", 15, 75);
|
g.setColor(1, 1, 0);
|
||||||
g.drawLine(35,80,40,80);
|
g.drawLine(GraphXZero, GraphYZero + GraphMarkerOffset, GraphXZero, GraphY100);
|
||||||
|
g.drawString("%", 39, GraphY100 - 10);
|
||||||
|
|
||||||
g.drawString("50", 20,125);
|
g.setFontAlign(1, -1, 0);
|
||||||
g.drawLine(35,130,40,130);
|
g.drawString("100", 30, GraphY100 - GraphMarkerOffset);
|
||||||
|
g.drawLine(GraphXZero - GraphMarkerOffset, GraphY100, GraphXZero, GraphY100);
|
||||||
|
|
||||||
g.drawString("0", 25, 175);
|
g.drawString("50", 30, GraphYZero - 50 - GraphMarkerOffset);
|
||||||
g.drawLine(35,180,210,180);
|
g.drawLine(GraphXZero - GraphMarkerOffset, 130, GraphXZero, 130);
|
||||||
|
|
||||||
g.drawString("Chart not yet functional", 60, 125);
|
g.drawString("0", 30, GraphYZero - GraphMarkerOffset);
|
||||||
|
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
g.setFontAlign(1, -1, 0);
|
||||||
|
g.drawLine(GraphXZero - GraphMarkerOffset, GraphYZero, GraphXMax + GraphMarkerOffset, GraphYZero);
|
||||||
|
|
||||||
|
// Right Y axis (Temperature)
|
||||||
|
g.setColor(0.4, 0.4, 1);
|
||||||
|
g.drawLine(GraphXMax, GraphYZero + GraphMarkerOffset, GraphXMax, GraphY100);
|
||||||
|
g.drawString("°C", GraphXMax + GraphMarkerOffset, GraphY100 - 10);
|
||||||
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
g.drawString("20", GraphXMax + 2 * GraphMarkerOffset, GraphYZero - GraphMarkerOffset);
|
||||||
|
|
||||||
|
g.drawLine(GraphXMax + GraphMarkerOffset, 130, GraphXMax, 130);
|
||||||
|
g.drawString("30", GraphXMax + 2 * GraphMarkerOffset, GraphYZero - 50 - GraphMarkerOffset);
|
||||||
|
|
||||||
|
g.drawLine(GraphXMax + GraphMarkerOffset, 80, GraphXMax, 80);
|
||||||
|
g.drawString("40", GraphXMax + 2 * GraphMarkerOffset, GraphY100 - GraphMarkerOffset);
|
||||||
|
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
}
|
||||||
|
|
||||||
|
function decrementDay(dayToDecrement) {
|
||||||
|
return dayToDecrement === 0 ? 6 : dayToDecrement-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadData() {
|
||||||
|
const startingDay = new Date().getDay();
|
||||||
|
|
||||||
|
// Load data for the current day
|
||||||
|
let logFileName = "bclog" + startingDay;
|
||||||
|
|
||||||
|
let dataLines = loadLinesFromFile(MaxValueCount, logFileName);
|
||||||
|
|
||||||
|
// Top up to MaxValueCount from previous days as required
|
||||||
|
let previousDay = decrementDay(startingDay);
|
||||||
|
while (dataLines.length < MaxValueCount
|
||||||
|
&& previousDay !== startingDay) {
|
||||||
|
|
||||||
|
let topUpLogFileName = "bclog" + previousDay;
|
||||||
|
let remainingLines = MaxValueCount - dataLines.length;
|
||||||
|
let topUpLines = loadLinesFromFile(remainingLines, topUpLogFileName);
|
||||||
|
|
||||||
|
if(topUpLines) {
|
||||||
|
dataLines = topUpLines.concat(dataLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
previousDay = decrementDay(previousDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadLinesFromFile(requestedLineCount, fileName) {
|
||||||
|
let allLines = [];
|
||||||
|
let returnLines = [];
|
||||||
|
|
||||||
|
var readFile = Storage.open(fileName, "r");
|
||||||
|
|
||||||
|
while ((nextLine = readFile.readLine())) {
|
||||||
|
if(nextLine) {
|
||||||
|
allLines.push(nextLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readFile = null;
|
||||||
|
|
||||||
|
if (allLines.length <= 0) return;
|
||||||
|
|
||||||
|
let linesToReadCount = Math.min(requestedLineCount, allLines.length);
|
||||||
|
let startingLineIndex = Math.max(0, allLines.length - requestedLineCount - 1);
|
||||||
|
|
||||||
|
for (let i = startingLineIndex; i < linesToReadCount + startingLineIndex; i++) {
|
||||||
|
if(allLines[i]) {
|
||||||
|
returnLines.push(allLines[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allLines = null;
|
||||||
|
|
||||||
|
return returnLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderData(dataArray) {
|
||||||
|
const switchableConsumers = {
|
||||||
|
none: 0,
|
||||||
|
lcd: 1,
|
||||||
|
compass: 2,
|
||||||
|
bluetooth: 4,
|
||||||
|
gps: 8,
|
||||||
|
hrm: 16
|
||||||
|
};
|
||||||
|
|
||||||
|
//const timestampIndex = 0;
|
||||||
|
const batteryIndex = 1;
|
||||||
|
const temperatureIndex = 2;
|
||||||
|
const switchabelsIndex = 3;
|
||||||
|
|
||||||
|
var allConsumers = switchableConsumers.none | switchableConsumers.lcd | switchableConsumers.compass | switchableConsumers.bluetooth | switchableConsumers.gps | switchableConsumers.hrm;
|
||||||
|
|
||||||
|
for (let i = 0; i < dataArray.length; i++) {
|
||||||
|
const element = dataArray[i];
|
||||||
|
|
||||||
|
var dataInfo = element.split(",");
|
||||||
|
|
||||||
|
// Battery percentage
|
||||||
|
g.setColor(1, 1, 0);
|
||||||
|
g.setPixel(GraphXZero + i, GraphYZero - parseInt(dataInfo[batteryIndex]));
|
||||||
|
|
||||||
|
// Temperature
|
||||||
|
g.setColor(0.4, 0.4, 1);
|
||||||
|
let scaledTemp = Math.floor(((parseFloat(dataInfo[temperatureIndex]) * 100) - 2000)/20) + ((((parseFloat(dataInfo[temperatureIndex]) * 100) - 2000) % 100)/25);
|
||||||
|
|
||||||
|
g.setPixel(GraphXZero + i, GraphYZero - scaledTemp);
|
||||||
|
|
||||||
|
// LCD state
|
||||||
|
if (parseInt(dataInfo[switchabelsIndex]) & switchableConsumers.lcd) {
|
||||||
|
g.setColor(1, 1, 1);
|
||||||
|
g.setFontAlign(1, -1, 0);
|
||||||
|
g.drawString("LCD", GraphXZero - GraphMarkerOffset, GraphLcdY - 2, true);
|
||||||
|
g.drawLine(GraphXZero + i, GraphLcdY, GraphXZero + i, GraphLcdY + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compass state
|
||||||
|
if (parseInt(dataInfo[switchabelsIndex]) & switchableConsumers.compass) {
|
||||||
|
g.setColor(0, 1, 0);
|
||||||
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
g.drawString("Compass", GraphXMax + GraphMarkerOffset, GraphCompassY - 2, true);
|
||||||
|
g.drawLine(GraphXZero + i, GraphCompassY, GraphXZero + i, GraphCompassY + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// // Bluetooth state
|
||||||
|
// if (switchables & switchableConsumers.lcd == switchableConsumers.lcd) {
|
||||||
|
// g.setColor(0, 0, 1);
|
||||||
|
// g.setFontAlign(1, -1, 0);
|
||||||
|
// g.drawString("BLE", GraphXZero - GraphMarkerOffset, GraphBluetoothY - 2, true);
|
||||||
|
// g.drawLine(GraphXZero + i, GraphBluetoothY, GraphXZero + i, GraphBluetoothY + 1);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Gps state
|
||||||
|
if (parseInt(dataInfo[switchabelsIndex]) & switchableConsumers.gps) {
|
||||||
|
g.setColor(0.8, 0.5, 0.24);
|
||||||
|
g.setFontAlign(-1, -1, 0);
|
||||||
|
g.drawString("GPS", GraphXMax + GraphMarkerOffset, GraphGpsY - 2, true);
|
||||||
|
g.drawLine(GraphXZero + i, GraphGpsY, GraphXZero + i, GraphGpsY + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hrm state
|
||||||
|
if (parseInt(dataInfo[switchabelsIndex]) & switchableConsumers.hrm) {
|
||||||
|
g.setColor(1, 0, 0);
|
||||||
|
g.setFontAlign(1, -1, 0);
|
||||||
|
g.drawString("HRM", GraphXZero - GraphMarkerOffset, GraphHrmY - 2, true);
|
||||||
|
g.drawLine(GraphXZero + i, GraphHrmY, GraphXZero + i, GraphHrmY + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dataArray = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderHomeIcon() {
|
||||||
|
//Home for Btn2
|
||||||
|
g.setColor(1, 1, 1);
|
||||||
|
g.drawLine(220, 118, 227, 110);
|
||||||
|
g.drawLine(227, 110, 234, 118);
|
||||||
|
|
||||||
|
g.drawPoly([222,117,222,125,232,125,232,117], false);
|
||||||
|
g.drawRect(226,120,229,125);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBatteryChart() {
|
||||||
|
renderCoordinateSystem();
|
||||||
|
let data = loadData();
|
||||||
|
renderData(data);
|
||||||
|
data = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
function switchOffApp(){
|
||||||
|
Bangle.showLauncher();
|
||||||
}
|
}
|
||||||
|
|
||||||
// special function to handle display switch on
|
// special function to handle display switch on
|
||||||
|
@ -22,13 +215,20 @@ Bangle.on('lcdPower', (on) => {
|
||||||
if (on) {
|
if (on) {
|
||||||
// call your app function here
|
// call your app function here
|
||||||
// If you clear the screen, do Bangle.drawWidgets();
|
// If you clear the screen, do Bangle.drawWidgets();
|
||||||
|
g.clear();
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
renderBatteryChart();
|
renderBatteryChart();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setWatch(switchOffApp, BTN2, {edge:"rising", debounce:50, repeat:true});
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
// call your app function here
|
// call your app function here
|
||||||
|
|
||||||
|
renderHomeIcon();
|
||||||
|
|
||||||
renderBatteryChart();
|
renderBatteryChart();
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
(() => {
|
(() => {
|
||||||
var switchableConsumers = {
|
const Storage = require("Storage");
|
||||||
|
|
||||||
|
const switchableConsumers = {
|
||||||
none: 0,
|
none: 0,
|
||||||
lcd: 1,
|
lcd: 1,
|
||||||
compass: 2,
|
compass: 2,
|
||||||
|
@ -8,59 +10,106 @@
|
||||||
hrm: 16
|
hrm: 16
|
||||||
};
|
};
|
||||||
|
|
||||||
var settings = {};
|
|
||||||
var batChartFile; // file for battery percentage recording
|
var batChartFile; // file for battery percentage recording
|
||||||
const recordingInterval10Min = 60 * 10 * 1000;
|
const recordingInterval10Min = 60 * 10 * 1000;
|
||||||
const recordingInterval1Min = 60*1000; //For testing
|
const recordingInterval1Min = 60*1000; //For testing
|
||||||
const recordingInterval10S = 10*1000; //For testing
|
const recordingInterval10S = 10*1000; //For testing
|
||||||
var recordingInterval = null;
|
var recordingInterval = null;
|
||||||
|
|
||||||
|
var compassEventReceived = false;
|
||||||
|
var gpsEventReceived = false;
|
||||||
|
var hrmEventReceived = false;
|
||||||
|
|
||||||
// draw your widget
|
// draw your widget
|
||||||
function draw() {
|
function draw() {
|
||||||
|
let x = this.x;
|
||||||
|
let y = this.y;
|
||||||
|
|
||||||
|
g.setColor(0, 1, 0);
|
||||||
|
g.fillPoly([x+5, y, x+5, y+4, x+1, y+4, x+1, y+20, x+18, y+20, x+18, y+4, x+13, y+4, x+13, y], true);
|
||||||
|
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
g.drawPoly([x+5, y+6, x+8, y+12, x+13, y+12, x+16, y+18], false);
|
||||||
|
|
||||||
g.reset();
|
g.reset();
|
||||||
g.drawString("BC", this.x, this.y);
|
}
|
||||||
|
|
||||||
|
function onMag(){
|
||||||
|
compassEventReceived = true;
|
||||||
|
// Stop handling events when no longer necessarry
|
||||||
|
Bangle.removeListener("mag", onMag);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onGps() {
|
||||||
|
gpsEventReceived = true;
|
||||||
|
Bangle.removeListener("GPS", onGps);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onHrm() {
|
||||||
|
hrmEventReceived = true;
|
||||||
|
Bangle.removeListener("HRM", onHrm);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEnabledConsumersValue() {
|
function getEnabledConsumersValue() {
|
||||||
var enabledConsumers = switchableConsumers.none;
|
var enabledConsumers = switchableConsumers.none;
|
||||||
|
|
||||||
|
Bangle.on('mag', onMag);
|
||||||
|
Bangle.on('GPS', onGps);
|
||||||
|
Bangle.on('HRM', onHrm);
|
||||||
|
|
||||||
|
// Wait two seconds, that should be enough for each of the events to get raised once
|
||||||
|
setTimeout(() => {
|
||||||
|
Bangle.removeAllListeners();
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
if (Bangle.isLCDOn())
|
if (Bangle.isLCDOn())
|
||||||
enabledConsumers = enabledConsumers | switchableConsumers.lcd;
|
enabledConsumers = enabledConsumers | switchableConsumers.lcd;
|
||||||
// Already added in the hope they will be available soon to get more details
|
// Already added in the hope they will be available soon to get more details
|
||||||
// if (Bangle.isCompassOn())
|
if (compassEventReceived)
|
||||||
// enabledConsumers = enabledConsumers | switchableConsumers.compass;
|
enabledConsumers = enabledConsumers | switchableConsumers.compass;
|
||||||
// if (Bangle.isBluetoothOn())
|
if (gpsEventReceived)
|
||||||
|
enabledConsumers = enabledConsumers | switchableConsumers.gps;
|
||||||
|
if (hrmEventReceived)
|
||||||
|
enabledConsumers = enabledConsumers | switchableConsumers.hrm;
|
||||||
|
//if (Bangle.isBluetoothOn())
|
||||||
// enabledConsumers = enabledConsumers | switchableConsumers.bluetooth;
|
// enabledConsumers = enabledConsumers | switchableConsumers.bluetooth;
|
||||||
// if (Bangle.isGpsOn())
|
|
||||||
// enabledConsumers = enabledConsumers | switchableConsumers.gps;
|
|
||||||
// if (Bangle.isHrmOn())
|
|
||||||
// enabledConsumers = enabledConsumers | switchableConsumers.hrm;
|
|
||||||
|
|
||||||
return enabledConsumers;
|
// Reset the event registration vars
|
||||||
|
compassEventReceived = false;
|
||||||
|
gpsEventReceived = false;
|
||||||
|
hrmEventReceived = false;
|
||||||
|
|
||||||
|
return enabledConsumers.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
function logBatteryData() {
|
function logBatteryData() {
|
||||||
const previousWriteLogName = "bcprvday";
|
const previousWriteLogName = "bcprvday";
|
||||||
const previousWriteDay = require("Storage").read(previousWriteLogName);
|
const previousWriteDay = parseInt(Storage.open(previousWriteLogName, "r").readLine());
|
||||||
const currentWriteDay = new Date().getDay();
|
const currentWriteDay = new Date().getDay();
|
||||||
|
|
||||||
const logFileName = "bclog" + currentWriteDay;
|
const logFileName = "bclog" + currentWriteDay;
|
||||||
|
|
||||||
// Change log target on day change
|
// Change log target on day change
|
||||||
if (previousWriteDay != currentWriteDay) {
|
if (!isNaN(previousWriteDay)
|
||||||
|
&& previousWriteDay != currentWriteDay) {
|
||||||
//Remove a log file containing data from a week ago
|
//Remove a log file containing data from a week ago
|
||||||
require("Storage").erase(logFileName);
|
Storage.open(logFileName, "r").erase();
|
||||||
require("Storage").write(previousWriteLogName, currentWriteDay);
|
Storage.open(previousWriteLogName, "w").write(parseInt(currentWriteDay));
|
||||||
}
|
}
|
||||||
|
|
||||||
var bcLogFileA = require("Storage").open(logFileName, "a");
|
var bcLogFileA = Storage.open(logFileName, "a");
|
||||||
if (bcLogFileA) {
|
if (bcLogFileA) {
|
||||||
console.log([getTime().toFixed(0), E.getBattery(), E.getTemperature(), getEnabledConsumersValue()].join(","));
|
let logTime = getTime().toFixed(0);
|
||||||
bcLogFileA.write([[getTime().toFixed(0), E.getBattery(), E.getTemperature(), getEnabledConsumersValue()].join(",")].join(",")+"\n");
|
let logPercent = E.getBattery();
|
||||||
|
let logTemperature = E.getTemperature();
|
||||||
|
let logConsumers = getEnabledConsumersValue();
|
||||||
|
|
||||||
|
let logString = [logTime, logPercent, logTemperature, logConsumers].join(",");
|
||||||
|
|
||||||
|
bcLogFileA.write(logString + "\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Called by the heart app to reload settings and decide what's
|
|
||||||
function reload() {
|
function reload() {
|
||||||
WIDGETS["batchart"].width = 24;
|
WIDGETS["batchart"].width = 24;
|
||||||
|
|
||||||
|
@ -74,6 +123,7 @@
|
||||||
reload();
|
reload();
|
||||||
Bangle.drawWidgets(); // relayout all widgets
|
Bangle.drawWidgets(); // relayout all widgets
|
||||||
}};
|
}};
|
||||||
|
|
||||||
// load settings, set correct widget width
|
// load settings, set correct widget width
|
||||||
reload();
|
reload();
|
||||||
})()
|
})()
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
|
@ -0,0 +1,352 @@
|
||||||
|
/**
|
||||||
|
* BangleJS Calculator
|
||||||
|
*
|
||||||
|
* Original Author: Frederic Rousseau https://github.com/fredericrous
|
||||||
|
* Created: April 2020
|
||||||
|
*/
|
||||||
|
|
||||||
|
g.clear();
|
||||||
|
Graphics.prototype.setFont7x11Numeric7Seg = function() {
|
||||||
|
this.setFontCustom(atob("ACAB70AYAwBgC94AAAAAAAAAAB7wAAPQhhDCGELwAAAAhDCGEMIXvAAeACAEAIAQPeAA8CEMIYQwhA8AB70IYQwhhCB4AAAIAQAgBAB7wAHvQhhDCGEL3gAPAhDCGEMIXvAAe9CCEEIIQPeAA94EIIQQghA8AB70AYAwBgCAAAAHgQghBCCF7wAHvQhhDCGEIAAAPehBCCEEIAAAAA=="), 46, atob("AgAHBwcHBwcHBwcHAAAAAAAAAAcHBwcHBw=="), 11);
|
||||||
|
};
|
||||||
|
|
||||||
|
var DEFAULT_SELECTION = '5';
|
||||||
|
var BOTTOM_MARGIN = 10;
|
||||||
|
var RIGHT_MARGIN = 20;
|
||||||
|
var COLORS = {
|
||||||
|
// [normal, selected]
|
||||||
|
DEFAULT: ['#7F8183', '#A6A6A7'],
|
||||||
|
OPERATOR: ['#F99D1C', '#CA7F2A'],
|
||||||
|
SPECIAL: ['#65686C', '#7F8183']
|
||||||
|
};
|
||||||
|
|
||||||
|
var keys = {
|
||||||
|
'0': {
|
||||||
|
xy: [0, 200, 120, 240],
|
||||||
|
trbl: '2.00'
|
||||||
|
},
|
||||||
|
'.': {
|
||||||
|
xy: [120, 200, 180, 240],
|
||||||
|
trbl: '3=.0'
|
||||||
|
},
|
||||||
|
'=': {
|
||||||
|
xy: [181, 200, 240, 240],
|
||||||
|
trbl: '+==.',
|
||||||
|
color: COLORS.OPERATOR
|
||||||
|
},
|
||||||
|
'1': {
|
||||||
|
xy: [0, 160, 60, 200],
|
||||||
|
trbl: '4201'
|
||||||
|
},
|
||||||
|
'2': {
|
||||||
|
xy: [60, 160, 120, 200],
|
||||||
|
trbl: '5301'
|
||||||
|
},
|
||||||
|
'3': {
|
||||||
|
xy: [120, 160, 180, 200],
|
||||||
|
trbl: '6+.2'
|
||||||
|
},
|
||||||
|
'+': {
|
||||||
|
xy: [181, 160, 240, 200],
|
||||||
|
trbl: '-+=3',
|
||||||
|
color: COLORS.OPERATOR
|
||||||
|
},
|
||||||
|
'4': {
|
||||||
|
xy: [0, 120, 60, 160],
|
||||||
|
trbl: '7514'
|
||||||
|
},
|
||||||
|
'5': {
|
||||||
|
xy: [60, 120, 120, 160],
|
||||||
|
trbl: '8624'
|
||||||
|
},
|
||||||
|
'6': {
|
||||||
|
xy: [120, 120, 180, 160],
|
||||||
|
trbl: '9-35'
|
||||||
|
},
|
||||||
|
'-': {
|
||||||
|
xy: [181, 120, 240, 160],
|
||||||
|
trbl: '*-+6',
|
||||||
|
color: COLORS.OPERATOR
|
||||||
|
},
|
||||||
|
'7': {
|
||||||
|
xy: [0, 80, 60, 120],
|
||||||
|
trbl: 'R847'
|
||||||
|
},
|
||||||
|
'8': {
|
||||||
|
xy: [60, 80, 120, 120],
|
||||||
|
trbl: 'N957'
|
||||||
|
},
|
||||||
|
'9': {
|
||||||
|
xy: [120, 80, 180, 120],
|
||||||
|
trbl: '%*68'
|
||||||
|
},
|
||||||
|
'*': {
|
||||||
|
xy: [181, 80, 240, 120],
|
||||||
|
trbl: '/*-9',
|
||||||
|
color: COLORS.OPERATOR
|
||||||
|
},
|
||||||
|
'R': {
|
||||||
|
xy: [0, 40, 60, 79],
|
||||||
|
trbl: 'RN7R',
|
||||||
|
color: COLORS.SPECIAL,
|
||||||
|
val: 'AC'
|
||||||
|
},
|
||||||
|
'N': {
|
||||||
|
xy: [60, 40, 120, 79],
|
||||||
|
trbl: 'N%8R',
|
||||||
|
color: COLORS.SPECIAL,
|
||||||
|
val: '+/-'
|
||||||
|
},
|
||||||
|
'%': {
|
||||||
|
xy: [120, 40, 180, 79],
|
||||||
|
trbl: '%/9N',
|
||||||
|
color: COLORS.SPECIAL
|
||||||
|
},
|
||||||
|
'/': {
|
||||||
|
xy: [181, 40, 240, 79],
|
||||||
|
trbl: '//*%',
|
||||||
|
color: COLORS.OPERATOR
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var selected = DEFAULT_SELECTION;
|
||||||
|
var prevSelected = DEFAULT_SELECTION;
|
||||||
|
var prevNumber = null;
|
||||||
|
var currNumber = null;
|
||||||
|
var operator = null;
|
||||||
|
var results = null;
|
||||||
|
var isDecimal = false;
|
||||||
|
var hasPressedEquals = false;
|
||||||
|
|
||||||
|
function drawKey(name, k, selected) {
|
||||||
|
var rMargin = 0;
|
||||||
|
var bMargin = 0;
|
||||||
|
var color = k.color || COLORS.DEFAULT;
|
||||||
|
g.setColor(color[selected ? 1 : 0]);
|
||||||
|
g.setFont('Vector', 20);
|
||||||
|
g.fillRect(k.xy[0], k.xy[1], k.xy[2], k.xy[3]);
|
||||||
|
g.setColor(-1);
|
||||||
|
// correct margins to center the texts
|
||||||
|
if (name == '0') {
|
||||||
|
rMargin = (RIGHT_MARGIN * 2) - 7;
|
||||||
|
} else if (name === '/') {
|
||||||
|
rMargin = 5;
|
||||||
|
} else if (name === '*') {
|
||||||
|
bMargin = 5;
|
||||||
|
rMargin = 3;
|
||||||
|
} else if (name === '-') {
|
||||||
|
rMargin = 3;
|
||||||
|
} else if (name === 'R' || name === 'N') {
|
||||||
|
rMargin = k.val === 'C' ? 0 : -9;
|
||||||
|
} else if (name === '%') {
|
||||||
|
rMargin = -3;
|
||||||
|
}
|
||||||
|
g.drawString(k.val || name, k.xy[0] + RIGHT_MARGIN + rMargin, k.xy[1] + BOTTOM_MARGIN + bMargin);
|
||||||
|
}
|
||||||
|
|
||||||
|
function doMath(x, y, operator) {
|
||||||
|
// might not be a number due to display of dot "." algo
|
||||||
|
x = Number(x);
|
||||||
|
y = Number(y);
|
||||||
|
switch (operator) {
|
||||||
|
case '/':
|
||||||
|
return x / y;
|
||||||
|
case '*':
|
||||||
|
return x * y;
|
||||||
|
case '+':
|
||||||
|
return x + y;
|
||||||
|
case '-':
|
||||||
|
return x - y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayOutput(num) {
|
||||||
|
var len;
|
||||||
|
var minusMarge = 0;
|
||||||
|
g.setColor(0);
|
||||||
|
g.fillRect(0, 0, 240, 39);
|
||||||
|
g.setColor(-1);
|
||||||
|
if (num === Infinity || num === -Infinity || isNaN(num)) {
|
||||||
|
// handle division by 0
|
||||||
|
if (num === Infinity) {
|
||||||
|
num = 'INFINITY';
|
||||||
|
} else if (num === -Infinity) {
|
||||||
|
num = '-INFINITY';
|
||||||
|
} else {
|
||||||
|
num = 'NOT A NUMBER';
|
||||||
|
minusMarge = -25;
|
||||||
|
}
|
||||||
|
len = (num + '').length;
|
||||||
|
currNumber = null;
|
||||||
|
results = null;
|
||||||
|
isDecimal = false;
|
||||||
|
hasPressedEquals = false;
|
||||||
|
prevNumber = null;
|
||||||
|
operator = null;
|
||||||
|
keys.R.val = 'AC';
|
||||||
|
drawKey('R', keys.R);
|
||||||
|
g.setFont('Vector', 22);
|
||||||
|
} else {
|
||||||
|
// might not be a number due to display of dot "."
|
||||||
|
var numNumeric = Number(num);
|
||||||
|
|
||||||
|
if (typeof num === 'string') {
|
||||||
|
if (num.indexOf('.') !== -1) {
|
||||||
|
// display a 0 before a lonely dot
|
||||||
|
if (numNumeric == 0) {
|
||||||
|
num = '0.';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// remove preceding 0
|
||||||
|
while (num.length > 1 && num[0] === '0')
|
||||||
|
num = num.substr(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
len = (num + '').length;
|
||||||
|
if (numNumeric < 0) {
|
||||||
|
// minus is not available in font 7x11Numeric7Seg, we use Vector
|
||||||
|
g.setFont('Vector', 20);
|
||||||
|
g.drawString('-', 220 - (len * 15), 10);
|
||||||
|
minusMarge = 15;
|
||||||
|
}
|
||||||
|
g.setFont('7x11Numeric7Seg', 2);
|
||||||
|
}
|
||||||
|
g.drawString(num, 220 - (len * 15) + minusMarge, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculatorLogic(x) {
|
||||||
|
if (hasPressedEquals) {
|
||||||
|
currNumber = results;
|
||||||
|
prevNumber = null;
|
||||||
|
operator = null;
|
||||||
|
results = null;
|
||||||
|
isDecimal = null;
|
||||||
|
displayOutput(currNumber);
|
||||||
|
hasPressedEquals = false;
|
||||||
|
}
|
||||||
|
if (prevNumber != null && currNumber != null && operator != null) {
|
||||||
|
// we execute the calculus only when there was a previous number entered before and an operator
|
||||||
|
results = doMath(prevNumber, currNumber, operator);
|
||||||
|
operator = x;
|
||||||
|
prevNumber = results;
|
||||||
|
currNumber = null;
|
||||||
|
displayOutput(results);
|
||||||
|
} else if (prevNumber == null && currNumber != null && operator == null) {
|
||||||
|
// no operator yet, save the current number for later use when an operator is pressed
|
||||||
|
operator = x;
|
||||||
|
prevNumber = currNumber;
|
||||||
|
currNumber = null;
|
||||||
|
displayOutput(prevNumber);
|
||||||
|
} else if (prevNumber == null && currNumber == null && operator == null) {
|
||||||
|
displayOutput(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buttonPress(val) {
|
||||||
|
switch (val) {
|
||||||
|
case 'R':
|
||||||
|
currNumber = null;
|
||||||
|
results = null;
|
||||||
|
isDecimal = false;
|
||||||
|
hasPressedEquals = false;
|
||||||
|
if (keys.R.val == 'AC') {
|
||||||
|
prevNumber = null;
|
||||||
|
operator = null;
|
||||||
|
} else {
|
||||||
|
keys.R.val = 'AC';
|
||||||
|
drawKey('R', keys.R);
|
||||||
|
}
|
||||||
|
displayOutput(0);
|
||||||
|
break;
|
||||||
|
case '%':
|
||||||
|
if (results != null) {
|
||||||
|
displayOutput(results /= 100);
|
||||||
|
} else if (currNumber != null) {
|
||||||
|
displayOutput(currNumber /= 100);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'N':
|
||||||
|
if (results != null) {
|
||||||
|
displayOutput(results *= -1);
|
||||||
|
} else if (currNumber != null) {
|
||||||
|
displayOutput(currNumber *= -1);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case '/':
|
||||||
|
case '*':
|
||||||
|
case '-':
|
||||||
|
case '+':
|
||||||
|
calculatorLogic(val);
|
||||||
|
break;
|
||||||
|
case '.':
|
||||||
|
keys.R.val = 'C';
|
||||||
|
drawKey('R', keys.R);
|
||||||
|
isDecimal = true;
|
||||||
|
displayOutput(currNumber == null ? 0 + '.' : currNumber + '.');
|
||||||
|
break;
|
||||||
|
case '=':
|
||||||
|
if (prevNumber != null && currNumber != null && operator != null) {
|
||||||
|
results = doMath(prevNumber, currNumber, operator);
|
||||||
|
prevNumber = results;
|
||||||
|
displayOutput(results);
|
||||||
|
hasPressedEquals = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
keys.R.val = 'C';
|
||||||
|
drawKey('R', keys.R);
|
||||||
|
if (isDecimal) {
|
||||||
|
currNumber = currNumber == null ? 0 + '.' + val : currNumber + '.' + val;
|
||||||
|
isDecimal = false;
|
||||||
|
} else {
|
||||||
|
currNumber = currNumber == null ? val : currNumber + val;
|
||||||
|
}
|
||||||
|
displayOutput(currNumber);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var k in keys) {
|
||||||
|
if (keys.hasOwnProperty(k)) {
|
||||||
|
drawKey(k, keys[k], k == '5');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g.setFont('7x11Numeric7Seg', 2.8);
|
||||||
|
g.drawString('0', 205, 10);
|
||||||
|
|
||||||
|
|
||||||
|
setWatch(function() {
|
||||||
|
drawKey(selected, keys[selected]);
|
||||||
|
// key 0 is 2 keys wide, go up to 1 if it was previously selected
|
||||||
|
if (selected == '0' && prevSelected === '1') {
|
||||||
|
prevSelected = selected;
|
||||||
|
selected = '1';
|
||||||
|
} else {
|
||||||
|
prevSelected = selected;
|
||||||
|
selected = keys[selected].trbl[0];
|
||||||
|
}
|
||||||
|
drawKey(selected, keys[selected], true);
|
||||||
|
}, BTN1, {repeat: true, debounce: 100});
|
||||||
|
|
||||||
|
setWatch(function() {
|
||||||
|
drawKey(selected, keys[selected]);
|
||||||
|
prevSelected = selected;
|
||||||
|
selected = keys[selected].trbl[2];
|
||||||
|
drawKey(selected, keys[selected], true);
|
||||||
|
}, BTN3, {repeat: true, debounce: 100});
|
||||||
|
|
||||||
|
Bangle.on('touch', function(direction) {
|
||||||
|
drawKey(selected, keys[selected]);
|
||||||
|
prevSelected = selected;
|
||||||
|
if (direction == 1) {
|
||||||
|
selected = keys[selected].trbl[3];
|
||||||
|
} else if (direction == 2) {
|
||||||
|
selected = keys[selected].trbl[1];
|
||||||
|
}
|
||||||
|
drawKey(selected, keys[selected], true);
|
||||||
|
});
|
||||||
|
|
||||||
|
setWatch(function() {
|
||||||
|
buttonPress(selected);
|
||||||
|
}, BTN2, {repeat: true, debounce: 100});
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwhBC/AC8r6/XlYvr64CEF9UrMIIv/R/7vTMwIAmlUklQGDroAFqwHGBRgJBqwMDq+k5nNABAWDC4QZFERAvGBQOBF5I0FCYNW1mImWs6+sDoQsDAYIJEAAeB2eB1mBA4QvF43P6/GF4mB6+BAQYlEro3BAAI3FDAezBYgvE43O64DBF4hbCAAMrGAIiFBYRUEHogaBxA6CF4vXLwPHF4giEDIIkDDgI2BFoI6FBgYWCF5PPF4rSBKwVWI4bAFFgdcYAykBX5HX53NFwfNfwIkDAQYAGBBAKCIIYABd4y9DAAJ9CAD9dF4gAGCIi8BABLXBBRQLEF4vHRwgvEERQ6DHpgvH66PB65fUBpZfJ4/G6wxBMIaPbL5QvB6/WF6hqNF5KPDF6jkGd6JeBF5AAdF4oAGDBeH1mHAAwIBF8esABQvdWQonDX4YvIYAq/GXobvNF4hfKCwwvF43GF5AXGL44vJLwgvE453DMIYuFR5JiHI4yPHRoaREIwpIFF7TvbR5BJCX5IvMADgvcroABF6vG4wvIX46DKBZYvEFwPHGAgZHERALRF4YuBHYIwEFxxfPF5CDDF6ZfLDAyPFFwovFKRYvV47vDAgIvRR5aOFL4orCFwbvHADYvEAA4YLdRYvQ45eBR5C6UF5vHX4LvJF8PGZYXXGAYvnLYYvfZ4xfXd6AvKGAK/RDAKNTF4wAG44="))
|
After Width: | Height: | Size: 10 KiB |
|
@ -7,3 +7,4 @@
|
||||||
0.06: Gadgetbridge App 'Connected' state is no longer toggleable
|
0.06: Gadgetbridge App 'Connected' state is no longer toggleable
|
||||||
0.07: Move configuration to settings menu
|
0.07: Move configuration to settings menu
|
||||||
0.08: Don't turn on LCD at start of every song
|
0.08: Don't turn on LCD at start of every song
|
||||||
|
0.09: Update Bluetooth connection state automatically
|
||||||
|
|
|
@ -189,8 +189,8 @@
|
||||||
g.flip(); // turns screen on
|
g.flip(); // turns screen on
|
||||||
}
|
}
|
||||||
|
|
||||||
NRF.on("connected", changedConnectionState);
|
NRF.on("connect", changedConnectionState);
|
||||||
NRF.on("disconnected", changedConnectionState);
|
NRF.on("disconnect", changedConnectionState);
|
||||||
|
|
||||||
WIDGETS["gbridgew"] = { area: "tl", width: 24, draw: draw };
|
WIDGETS["gbridgew"] = { area: "tl", width: 24, draw: draw };
|
||||||
|
|
||||||
|
|
|
@ -4,3 +4,4 @@
|
||||||
0.04: Properly Fix GPS time display in gpsrec app
|
0.04: Properly Fix GPS time display in gpsrec app
|
||||||
0.05: Tweaks for variable size widget system
|
0.05: Tweaks for variable size widget system
|
||||||
0.06: Ensure widget update itself (fix #118) and change to using icons
|
0.06: Ensure widget update itself (fix #118) and change to using icons
|
||||||
|
0.07: Added @jeffmer's awesome track viewer
|
||||||
|
|
|
@ -70,27 +70,65 @@ function viewTracks() {
|
||||||
return E.showMenu(menu);
|
return E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getTrackInfo(fn) {
|
||||||
|
var filename = getFN(fn);
|
||||||
|
var minLat = 90;
|
||||||
|
var maxLat = -90;
|
||||||
|
var minLong = 180;
|
||||||
|
var maxLong = -180;
|
||||||
|
var starttime, duration=0;
|
||||||
|
var f = require("Storage").open(filename,"r");
|
||||||
|
if (f===undefined) return;
|
||||||
|
var l = f.readLine(f);
|
||||||
|
var nl = 0, c, n;
|
||||||
|
if (l!==undefined) {
|
||||||
|
c = l.split(",");
|
||||||
|
starttime = parseInt(c[0]);
|
||||||
|
}
|
||||||
|
// pushed this loop together to try and bump loading speed a little
|
||||||
|
while(l!==undefined) {
|
||||||
|
++nl;c=l.split(",");
|
||||||
|
n = parseFloat(c[1]);if(n>maxLat)maxLat=n;if(n<minLat)minLat=n;
|
||||||
|
n = parseFloat(c[2]);if(n>maxLong)maxLong=n;if(n<minLong)minLong=n;
|
||||||
|
l = f.readLine(f);
|
||||||
|
}
|
||||||
|
if (c) duration = parseInt(c[0]) - starttime;
|
||||||
|
var lfactor = Math.cos(minLat*Math.PI/180);
|
||||||
|
var ylen = (maxLat-minLat);
|
||||||
|
var xlen = (maxLong-minLong)* lfactor;
|
||||||
|
var scale = xlen>ylen ? 200/xlen : 200/ylen;
|
||||||
|
return {
|
||||||
|
fn : fn,
|
||||||
|
filename : filename,
|
||||||
|
time : new Date(starttime),
|
||||||
|
records : nl,
|
||||||
|
minLat : minLat, maxLat : maxLat,
|
||||||
|
minLong : minLong, maxLong : maxLong,
|
||||||
|
lfactor : lfactor,
|
||||||
|
scale : scale,
|
||||||
|
duration : Math.round(duration/1000)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function asTime(v){
|
||||||
|
var mins = Math.floor(v/60);
|
||||||
|
var secs = v-mins*60;
|
||||||
|
return ""+mins.toString()+"m "+secs.toString()+"s";
|
||||||
|
}
|
||||||
|
|
||||||
function viewTrack(n) {
|
function viewTrack(n) {
|
||||||
|
E.showMessage("Loading...","GPS Track "+n);
|
||||||
|
var info = getTrackInfo(n);
|
||||||
const menu = {
|
const menu = {
|
||||||
'': { 'title': 'GPS Track '+n }
|
'': { 'title': 'GPS Track '+n }
|
||||||
};
|
};
|
||||||
var trackCount = 0;
|
if (info.time)
|
||||||
var trackTime;
|
menu[info.time.toISOString().substr(0,16).replace("T"," ")] = function(){};
|
||||||
var f = require("Storage").open(getFN(n),"r");
|
menu["Duration"] = { value : asTime(info.duration)};
|
||||||
var l = f.readLine();
|
menu["Records"] = { value : ""+info.records };
|
||||||
if (l!==undefined) {
|
menu['Plot'] = function() {
|
||||||
var c = l.split(",");
|
plotTrack(info);
|
||||||
trackTime = new Date(parseInt(c[0]));
|
};
|
||||||
}
|
|
||||||
while (l!==undefined) {
|
|
||||||
trackCount++;
|
|
||||||
// TODO: min/max/length of track?
|
|
||||||
l = f.readLine();
|
|
||||||
}
|
|
||||||
if (trackTime)
|
|
||||||
menu[" "+trackTime.toISOString().substr(0,16).replace("T"," ")] = function(){};
|
|
||||||
menu[trackCount+" records"] = function(){};
|
|
||||||
// TODO: option to draw it? Just scan through, project using min/max
|
|
||||||
menu['Erase'] = function() {
|
menu['Erase'] = function() {
|
||||||
E.showPrompt("Delete Track?").then(function(v) {
|
E.showPrompt("Delete Track?").then(function(v) {
|
||||||
if (v) {
|
if (v) {
|
||||||
|
@ -107,4 +145,80 @@ function viewTrack(n) {
|
||||||
return E.showMenu(menu);
|
return E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function plotTrack(info) {
|
||||||
|
function xcoord(long){
|
||||||
|
return 30 + Math.round((long-info.minLong)*info.lfactor*info.scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
function ycoord(lat){
|
||||||
|
return 210 - Math.round((lat - info.minLat)*info.scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
function radians(a) {
|
||||||
|
return a*Math.PI/180;
|
||||||
|
}
|
||||||
|
|
||||||
|
function distance(lat1,long1,lat2,long2){
|
||||||
|
var x = radians(long1-long2) * Math.cos(radians((lat1+lat2)/2));
|
||||||
|
var y = radians(lat2-lat1);
|
||||||
|
return Math.sqrt(x*x + y*y) * 6371000;
|
||||||
|
}
|
||||||
|
|
||||||
|
E.showMenu(); // remove menu
|
||||||
|
g.setColor(1,0.5,0.5);
|
||||||
|
g.setFont("Vector",16);
|
||||||
|
g.fillRect(9,80,11,120);
|
||||||
|
g.fillPoly([9,60,19,80,0,80]);
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
g.drawString("N",2,40);
|
||||||
|
g.drawString("Track"+info.fn.toString()+" - Loading",10,220);
|
||||||
|
g.setColor(0,0,0);
|
||||||
|
g.fillRect(0,220,239,239);
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
g.drawString(asTime(info.duration),10,220);
|
||||||
|
var f = require("Storage").open(info.filename,"r");
|
||||||
|
if (f===undefined) return;
|
||||||
|
var l = f.readLine(f);
|
||||||
|
var ox=0;
|
||||||
|
var oy=0;
|
||||||
|
var olat,olong,dist=0;
|
||||||
|
var first = true;
|
||||||
|
var i=0;
|
||||||
|
while(l!==undefined) {
|
||||||
|
var c = l.split(",");
|
||||||
|
var lat = parseFloat(c[1]);
|
||||||
|
var long = parseFloat(c[2]);
|
||||||
|
var x = xcoord(long);
|
||||||
|
var y = ycoord(lat);
|
||||||
|
if (first) {
|
||||||
|
g.moveTo(x,y);
|
||||||
|
g.setColor(0,1,0);
|
||||||
|
g.fillCircle(x,y,5);
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
first = false;
|
||||||
|
} else if (x!=ox || y!=oy) {
|
||||||
|
g.lineTo(x,y);
|
||||||
|
}
|
||||||
|
if (!first) {
|
||||||
|
var d = distance(olat,olong,lat,long);
|
||||||
|
if (!isNaN(d)) dist+=d;
|
||||||
|
}
|
||||||
|
olat = lat;
|
||||||
|
olong = long;
|
||||||
|
ox = x;
|
||||||
|
oy = y;
|
||||||
|
l = f.readLine(f);
|
||||||
|
}
|
||||||
|
g.setColor(1,0,0);
|
||||||
|
g.fillCircle(ox,oy,5);
|
||||||
|
g.setColor(1,1,1);
|
||||||
|
g.drawString(require("locale").distance(dist),120,220);
|
||||||
|
g.setFont("6x8",2);
|
||||||
|
g.setFontAlign(0,0,3);
|
||||||
|
g.drawString("Back",230,200);
|
||||||
|
setWatch(function() {
|
||||||
|
viewTrack(info.fn);
|
||||||
|
}, BTN3);
|
||||||
|
}
|
||||||
|
|
||||||
showMainMenu();
|
showMainMenu();
|
||||||
|
|
|
@ -6,4 +6,7 @@
|
||||||
0.06: Performance refactor, and enhanced graphics!
|
0.06: Performance refactor, and enhanced graphics!
|
||||||
0.07: Swipe right to change between Mario and Toad characters, swipe left to toggle night mode
|
0.07: Swipe right to change between Mario and Toad characters, swipe left to toggle night mode
|
||||||
0.08: Update date panel to be info panel toggling between Date, Battery and Temperature. Add Princes Daisy
|
0.08: Update date panel to be info panel toggling between Date, Battery and Temperature. Add Princes Daisy
|
||||||
0.09: Add GadgetBridge functionality. Mario shows message type in speach bubble, while message scrolls in info panel
|
0.09: Add GadgetBridge functionality. Mario shows message type in speach bubble, while message scrolls in info panel
|
||||||
|
0.10: Swiping left to enable night-mode now also reduces LCD brightness through 3 levels before returning to day-mode.
|
||||||
|
0.11: User settings persisted and read to file.
|
||||||
|
0.12: Add info banner message when phone (dis)connects. Display low-battery warning (<=10%)
|
|
@ -8,7 +8,7 @@ Enjoy watching Mario, or one of the other game characters run through a level wh
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* Multiple characters - swipe the screen right to change the character between `Mario`, `Toad`, and `Daisy`
|
* Multiple characters - swipe the screen right to change the character between `Mario`, `Toad`, and `Daisy`
|
||||||
* Night and Day modes - swipe left to toggle mode
|
* Night and Day modes - swipe left to enter night mode, with 3 levels of darkness before returning to day mode.
|
||||||
* Smooth animation
|
* Smooth animation
|
||||||
* Awesome 8-bit style grey-scale graphics
|
* Awesome 8-bit style grey-scale graphics
|
||||||
* Mario jumps to change the time, every minute
|
* Mario jumps to change the time, every minute
|
||||||
|
|
|
@ -16,6 +16,8 @@ const is12Hour = settings["12hour"] || false;
|
||||||
|
|
||||||
// Screen dimensions
|
// Screen dimensions
|
||||||
let W, H;
|
let W, H;
|
||||||
|
// Screen brightness
|
||||||
|
let brightness = 1;
|
||||||
|
|
||||||
let intervalRef, displayTimeoutRef = null;
|
let intervalRef, displayTimeoutRef = null;
|
||||||
|
|
||||||
|
@ -79,6 +81,16 @@ const phone = {
|
||||||
messageType: null,
|
messageType: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const SETTINGS_FILE = "marioclock.json";
|
||||||
|
|
||||||
|
function readSettings() {
|
||||||
|
return require('Storage').readJSON(SETTINGS_FILE, 1) || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeSettings(newSettings) {
|
||||||
|
require("Storage").writeJSON(SETTINGS_FILE, newSettings);
|
||||||
|
}
|
||||||
|
|
||||||
function phoneOutbound(msg) {
|
function phoneOutbound(msg) {
|
||||||
Bluetooth.println(JSON.stringify(msg));
|
Bluetooth.println(JSON.stringify(msg));
|
||||||
}
|
}
|
||||||
|
@ -164,7 +176,17 @@ function switchCharacter() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleNightMode() {
|
function toggleNightMode() {
|
||||||
nightMode = !nightMode;
|
if (!nightMode) {
|
||||||
|
nightMode = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
brightness -= 0.30;
|
||||||
|
if (brightness <= 0) {
|
||||||
|
brightness = 1;
|
||||||
|
nightMode = false;
|
||||||
|
}
|
||||||
|
Bangle.setLCDBrightness(brightness);
|
||||||
}
|
}
|
||||||
|
|
||||||
function incrementTimer() {
|
function incrementTimer() {
|
||||||
|
@ -324,16 +346,20 @@ function drawToadFrame(idx, x, y) {
|
||||||
function drawNotice(x, y) {
|
function drawNotice(x, y) {
|
||||||
if (phone.message === null) return;
|
if (phone.message === null) return;
|
||||||
|
|
||||||
|
let img;
|
||||||
switch (phone.messageType) {
|
switch (phone.messageType) {
|
||||||
case "call":
|
case "call":
|
||||||
const callImg = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INEw9cAAIPFBxAPEBw/WBxYACDrQ7QLI53OSpApDBoQAHB4INLByANNAwo="));
|
img = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INEw9cAAIPFBxAPEBw/WBxYACDrQ7QLI53OSpApDBoQAHB4INLByANNAwo="));
|
||||||
g.drawImage(callImg, characterSprite.x, characterSprite.y - 16);
|
|
||||||
break;
|
break;
|
||||||
case "notify":
|
case "notify":
|
||||||
const msgImg = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INCrgAHB4QOEDQgOIAIQFGBwovDA4gOGFooOVLJR3OSpApDBoQAHB4INLByANNAwoA="));
|
img = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INCrgAHB4QOEDQgOIAIQFGBwovDA4gOGFooOVLJR3OSpApDBoQAHB4INLByANNAwoA="));
|
||||||
g.drawImage(msgImg, characterSprite.x, characterSprite.y - 16);
|
break;
|
||||||
|
case "lowBatt":
|
||||||
|
img = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INFrgABB4oOEBoQPFBwwDGB0uHAAIOLJRB3OSpApDBoQAHB4INLByANNAwo"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (img) g.drawImage(img, characterSprite.x, characterSprite.y - 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawCharacter(date, character) {
|
function drawCharacter(date, character) {
|
||||||
|
@ -555,8 +581,39 @@ function startTimers(){
|
||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadSettings() {
|
||||||
|
const settings = readSettings();
|
||||||
|
if (!settings) return;
|
||||||
|
|
||||||
|
if (settings.character) characterSprite.character = settings.character;
|
||||||
|
if (settings.nightMode) nightMode = settings.nightMode;
|
||||||
|
if (settings.brightness) {
|
||||||
|
brightness = settings.brightness;
|
||||||
|
Bangle.setLCDBrightness(brightness);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSettings() {
|
||||||
|
const newSettings = {
|
||||||
|
character: characterSprite.character,
|
||||||
|
nightMode: nightMode,
|
||||||
|
brightness: brightness,
|
||||||
|
};
|
||||||
|
writeSettings(newSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkBatteryLevel() {
|
||||||
|
if (Bangle.isCharging()) return;
|
||||||
|
if (E.getBattery() > 10) return;
|
||||||
|
if (phone.message !== null) return;
|
||||||
|
|
||||||
|
phoneNewMessage("lowBatt", "Warning, battery is low");
|
||||||
|
}
|
||||||
|
|
||||||
// Main
|
// Main
|
||||||
function init() {
|
function init() {
|
||||||
|
loadSettings();
|
||||||
|
|
||||||
clearInterval();
|
clearInterval();
|
||||||
|
|
||||||
// Initialise display
|
// Initialise display
|
||||||
|
@ -606,23 +663,31 @@ function init() {
|
||||||
default:
|
default:
|
||||||
toggleNightMode();
|
toggleNightMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
updateSettings();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Phone connectivity
|
// Phone connectivity
|
||||||
try { NRF.wake(); } catch (e) {}
|
try { NRF.wake(); } catch (e) {}
|
||||||
|
|
||||||
NRF.on('disconnect', () => Bangle.buzz());
|
NRF.on('disconnect', () => {
|
||||||
|
phoneNewMessage(null, "Phone disconnected");
|
||||||
|
});
|
||||||
|
|
||||||
NRF.on('connect', () => {
|
NRF.on('connect', () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
phoneOutbound({ t: "status", bat: E.getBattery() });
|
phoneOutbound({ t: "status", bat: E.getBattery() });
|
||||||
}, ONE_SECOND * 2);
|
}, ONE_SECOND * 2);
|
||||||
Bangle.buzz();
|
phoneNewMessage(null, "Phone connected");
|
||||||
});
|
});
|
||||||
|
|
||||||
GB = (evt) => phoneInbound(evt);
|
GB = (evt) => phoneInbound(evt);
|
||||||
|
|
||||||
startTimers();
|
startTimers();
|
||||||
|
|
||||||
|
setInterval(checkBatteryLevel, ONE_SECOND * 60 * 10);
|
||||||
|
checkBatteryLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialise!
|
// Initialise!
|
||||||
init()
|
init();
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New App!
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwgFCiIABiAGFiINJAAUS///CAgGEgMT//zBoYXFmIiCC40fEooXF+QXJn4lCC5ARDC4oFC//xMAoXDJAQXFBgY9DC4wKCC4p2CPA4XDCQQXEOwXxPA4XBEQJICC4p2BmICCC44KBJAIXEiIJBkMvPAwXCWgYXFAgQMBPAoXCBwUxC4jtDeI4XDJAQXDFYXxHAoXGJAYXDLYPykUieIwXDJAYXDG4IAEPAgXCRgJICPYoAEPAgXDZ4TcDmYXGMAgXDUAZiEPwIABCALEBC5BZC+YQCRwRsEC45ID+S5BCAkBEYJ4DC4hID+IbCIAYjCCIYXGEgMxXoJwEgI3CA4JQDAAwaBmQGDFIQ3CC5UzkSLBdwIIDmYXCWY4jBCAJBCPYQ0EC5bXGkLuDC5QtEAAXzPoZMCmZwB+YFCbYkykQFCVoZMDWALnDQwRjDeoZIDZAgJCWwYeBFATWFC5LuHawgXKdwyJDD4YXIOAMzH4gICmIXKEwQXXkQXFKAKQFC85HNO64XDU44XMX48Sa5zvCmJICA4YXLE4fziIACJ4PyM4gXHCAQwBCwI2GC5JADAApGFC5ERmYWFFwwXHDARJCMgYWFB4MTmYiFLgMjCwMyiIuGE4QABNIyPDBQgA=="))
|
|
@ -0,0 +1,106 @@
|
||||||
|
// Teach a user the NATO Phonetic Alphabet + numbers
|
||||||
|
// Based on the Morse Code app
|
||||||
|
|
||||||
|
const FONT_NAME = 'Vector12';
|
||||||
|
const FONT_SIZE = 80;
|
||||||
|
const SCREEN_PIXELS = 240;
|
||||||
|
const UNIT = 100;
|
||||||
|
const NATO_MAP = {
|
||||||
|
A: 'ALFA',
|
||||||
|
B: 'BRAVO',
|
||||||
|
C: 'CHARLIE',
|
||||||
|
D: 'DELTA',
|
||||||
|
E: 'ECHO',
|
||||||
|
F: 'FOXTROT',
|
||||||
|
G: 'GOLF',
|
||||||
|
H: 'HOTEL',
|
||||||
|
I: 'INDIA',
|
||||||
|
J: 'JULIETT',
|
||||||
|
K: 'KILO',
|
||||||
|
L: 'LIMA',
|
||||||
|
M: 'MIKE',
|
||||||
|
N: 'NOVEMBER',
|
||||||
|
O: 'OSCAR',
|
||||||
|
P: 'PAPA',
|
||||||
|
Q: 'QUEBEC',
|
||||||
|
R: 'ROMEO',
|
||||||
|
S: 'SIERRA',
|
||||||
|
T: 'TANGO',
|
||||||
|
U: 'UNIFORM',
|
||||||
|
V: 'VICTOR',
|
||||||
|
W: 'WHISKEY',
|
||||||
|
X: 'X-RAY',
|
||||||
|
Y: 'YANKEE',
|
||||||
|
Z: 'ZULU',
|
||||||
|
'0': 'ZE-RO',
|
||||||
|
'1': 'WUN',
|
||||||
|
'2': 'TOO',
|
||||||
|
'3': 'TREE',
|
||||||
|
'4': 'FOW-ER',
|
||||||
|
'5': 'FIFE',
|
||||||
|
'6': 'SIX',
|
||||||
|
'7': 'SEV-EN',
|
||||||
|
'8': 'AIT',
|
||||||
|
'9': 'NIN-ER',
|
||||||
|
};
|
||||||
|
|
||||||
|
let INDEX = 0;
|
||||||
|
let showLetter = true;
|
||||||
|
|
||||||
|
const writeText = (txt) => {
|
||||||
|
g.clear();
|
||||||
|
g.setFont(FONT_NAME, FONT_SIZE);
|
||||||
|
|
||||||
|
var width = g.stringWidth(txt);
|
||||||
|
|
||||||
|
// Fit text to screen
|
||||||
|
var fontFix = FONT_SIZE;
|
||||||
|
while(width > SCREEN_PIXELS-10){
|
||||||
|
fontFix--;
|
||||||
|
g.setFont(FONT_NAME, fontFix);
|
||||||
|
width = g.stringWidth(txt);
|
||||||
|
}
|
||||||
|
g.drawString(txt, (SCREEN_PIXELS / 2) - (width / 2), SCREEN_PIXELS / 2);
|
||||||
|
};
|
||||||
|
const writeLetter = () => {
|
||||||
|
writeText(Object.keys(NATO_MAP)[INDEX]);
|
||||||
|
};
|
||||||
|
const writeCode = () => {
|
||||||
|
writeText(NATO_MAP[Object.keys(NATO_MAP)[INDEX]]);
|
||||||
|
};
|
||||||
|
const toggle = () => {
|
||||||
|
showLetter = !showLetter;
|
||||||
|
if(showLetter){
|
||||||
|
writeLetter();
|
||||||
|
}else {
|
||||||
|
writeCode();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Bootstrapping
|
||||||
|
|
||||||
|
g.clear();
|
||||||
|
g.setFont(FONT_NAME, FONT_SIZE);
|
||||||
|
g.setColor(0, 1, 0);
|
||||||
|
g.setFontAlign(-1, 0, 0);
|
||||||
|
|
||||||
|
|
||||||
|
const step = (positive) => () => {
|
||||||
|
if (positive) {
|
||||||
|
INDEX = INDEX + 1;
|
||||||
|
if (INDEX > Object.keys(NATO_MAP).length - 1) INDEX = 0;
|
||||||
|
} else {
|
||||||
|
INDEX = INDEX - 1;
|
||||||
|
if (INDEX < 0) INDEX = Object.keys(NATO_MAP).length - 1;
|
||||||
|
}
|
||||||
|
showLetter = true; // for toggle()
|
||||||
|
writeLetter();
|
||||||
|
};
|
||||||
|
|
||||||
|
writeLetter();
|
||||||
|
|
||||||
|
// Press the middle button to see the NATO Phonetic wording
|
||||||
|
setWatch(toggle, BTN2, { repeat: true });
|
||||||
|
// Allow user to switch between letters
|
||||||
|
setWatch(step(true), BTN1, { repeat: true });
|
||||||
|
setWatch(step(false), BTN3, { repeat: true });
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -2,3 +2,6 @@
|
||||||
Renamed as nodeconf-specific
|
Renamed as nodeconf-specific
|
||||||
0.03: Move configuration into App/widget settings
|
0.03: Move configuration into App/widget settings
|
||||||
Move loader into welcome.boot.js
|
Move loader into welcome.boot.js
|
||||||
|
0.04: Run again when updated
|
||||||
|
Don't run again when settings app is updated (or absent)
|
||||||
|
Add "Run Now" option to settings
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
(function() {
|
(function() {
|
||||||
let s = require('Storage').readJSON('setting.json', 1) || {}
|
let s = require('Storage').readJSON('ncstart.settings.json', 1)
|
||||||
|
|| require('Storage').readJSON('setting.json', 1)
|
||||||
|
|| {welcomed: true} // do NOT run if global settings are also absent
|
||||||
if (!s.welcomed && require('Storage').read('ncstart.app.js')) {
|
if (!s.welcomed && require('Storage').read('ncstart.app.js')) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
s.welcomed = true
|
s.welcomed = true
|
||||||
require('Storage').write('setting.json', s)
|
require('Storage').write('ncstart.settings.json', s)
|
||||||
load('ncstart.app.js')
|
load('ncstart.app.js')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"welcomed": false
|
||||||
|
}
|
|
@ -1,16 +1,15 @@
|
||||||
// The welcome app is special, and gets to use global settings
|
// The welcome app is special, and gets to use global settings
|
||||||
(function(back) {
|
(function(back) {
|
||||||
let settings = require('Storage').readJSON('setting.json', 1) || {}
|
let settings = require('Storage').readJSON('ncstart.settings.json', 1)
|
||||||
|
|| require('Storage').readJSON('setting.json', 1) || {}
|
||||||
E.showMenu({
|
E.showMenu({
|
||||||
'': { 'title': 'NCEU Startup' },
|
'': { 'title': 'NCEU Startup' },
|
||||||
'Run again': {
|
'Run on Next Boot': {
|
||||||
value: !settings.welcomed,
|
value: !settings.welcomed,
|
||||||
format: v => v ? 'Yes' : 'No',
|
format: v => v ? 'OK' : 'No',
|
||||||
onchange: v => {
|
onchange: v => require('Storage').write('ncstart.settings.json', {welcomed: !v}),
|
||||||
settings.welcomed = v ? undefined : true
|
|
||||||
require('Storage').write('setting.json', settings)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
'Run Now': () => load('ncstart.app.js'),
|
||||||
'< Back': back,
|
'< Back': back,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
|
0.02: Use BTN2 for settings menu like other clocks
|
||||||
|
|
|
@ -11,7 +11,7 @@ var numerals = {
|
||||||
1:[[59,1,82,1,90,9,90,82,82,90,73,90,65,82,65,27,59,27,51,19,51,9,59,1]],
|
1:[[59,1,82,1,90,9,90,82,82,90,73,90,65,82,65,27,59,27,51,19,51,9,59,1]],
|
||||||
2:[[9,1,82,1,90,9,90,47,82,55,21,55,21,64,82,64,90,72,90,82,82,90,9,90,1,82,1,43,9,35,70,35,70,25,9,25,1,17,1,9,9,1]],
|
2:[[9,1,82,1,90,9,90,47,82,55,21,55,21,64,82,64,90,72,90,82,82,90,9,90,1,82,1,43,9,35,70,35,70,25,9,25,1,17,1,9,9,1]],
|
||||||
3:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,74,9,66,70,66,70,57,9,57,1,49,1,41,9,33,70,33,70,25,9,25,1,17,1,9,9,1]],
|
3:[[9,1,82,1,90,9,90,82,82,90,9,90,1,82,1,74,9,66,70,66,70,57,9,57,1,49,1,41,9,33,70,33,70,25,9,25,1,17,1,9,9,1]],
|
||||||
4:[[9,1,14,1,22,9,22,34,69,34,69,9,77,1,82,1,90,9,90,82,82,90,78,90,70,82,70,55,9,55,1,47,1,9,9,1]],
|
4:[[9,1,14,1,22,9,22,34,69,34,69,9,77,1,82,1,90,9,90,82,82,90,78,90,70,82,70,55,9,55,1,47,1,9,9,1]],
|
||||||
5:[[9,1,82,1,90,9,90,17,82,25,21,25,21,35,82,35,90,43,90,82,82,90,9,90,1,82,1,72,9,64,71,64,71,55,9,55,1,47,1,9,9,1]],
|
5:[[9,1,82,1,90,9,90,17,82,25,21,25,21,35,82,35,90,43,90,82,82,90,9,90,1,82,1,72,9,64,71,64,71,55,9,55,1,47,1,9,9,1]],
|
||||||
6:[[9,1,82,1,90,9,90,14,82,22,22,22,22,36,82,36,90,44,90,82,82,90,9,90,1,82,1,9,9,1],[22,55,69,55,69,69,22,69,22,55]],
|
6:[[9,1,82,1,90,9,90,14,82,22,22,22,22,36,82,36,90,44,90,82,82,90,9,90,1,82,1,9,9,1],[22,55,69,55,69,69,22,69,22,55]],
|
||||||
7:[[9,1,82,1,90,9,90,15,15,90,9,90,1,82,1,76,54,23,9,23,1,15,1,9,9,1]],
|
7:[[9,1,82,1,90,9,90,15,15,90,9,90,1,82,1,76,54,23,9,23,1,15,1,9,9,1]],
|
||||||
|
@ -47,7 +47,7 @@ if (!settings) {
|
||||||
function drawNum(num,col,x,y,func){
|
function drawNum(num,col,x,y,func){
|
||||||
g.setColor(col);
|
g.setColor(col);
|
||||||
let tx = x*100+35;
|
let tx = x*100+35;
|
||||||
let ty = y*100+35;
|
let ty = y*100+35;
|
||||||
for (let i=0;i<numerals[num].length;i++){
|
for (let i=0;i<numerals[num].length;i++){
|
||||||
if (i>0) g.setColor((func==fill)?"#000000":col);
|
if (i>0) g.setColor((func==fill)?"#000000":col);
|
||||||
func(translate(tx, ty,numerals[num][i]));
|
func(translate(tx, ty,numerals[num][i]));
|
||||||
|
@ -57,7 +57,7 @@ function drawNum(num,col,x,y,func){
|
||||||
function draw(drawMode){
|
function draw(drawMode){
|
||||||
let d = new Date();
|
let d = new Date();
|
||||||
let h1 = Math.floor(d.getHours()/10);
|
let h1 = Math.floor(d.getHours()/10);
|
||||||
let h2 = d.getHours()%10;
|
let h2 = d.getHours()%10;
|
||||||
let m1 = Math.floor(d.getMinutes()/10);
|
let m1 = Math.floor(d.getMinutes()/10);
|
||||||
let m2 = d.getMinutes()%10;
|
let m2 = d.getMinutes()%10;
|
||||||
g.clearRect(0,24,240,240);
|
g.clearRect(0,24,240,240);
|
||||||
|
@ -70,9 +70,9 @@ function draw(drawMode){
|
||||||
Bangle.setLCDMode();
|
Bangle.setLCDMode();
|
||||||
|
|
||||||
clearWatch();
|
clearWatch();
|
||||||
setWatch(Bangle.showLauncher, BTN1, {repeat:false,edge:"falling"});
|
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
||||||
|
|
||||||
g.clear();
|
g.clear();
|
||||||
clearInterval();
|
clearInterval();
|
||||||
if (settings.color>0) _rCol=settings.color-1;
|
if (settings.color>0) _rCol=settings.color-1;
|
||||||
interval=setInterval(draw, REFRESH_RATE, settings.drawMode);
|
interval=setInterval(draw, REFRESH_RATE, settings.drawMode);
|
||||||
|
@ -80,7 +80,7 @@ draw(settings.drawMode);
|
||||||
|
|
||||||
Bangle.on('lcdPower', function(on) {
|
Bangle.on('lcdPower', function(on) {
|
||||||
if (on) {
|
if (on) {
|
||||||
if (settings.color==0) _rCol = Math.floor(Math.random()*_hCol.length);
|
if (settings.color==0) _rCol = Math.floor(Math.random()*_hCol.length);
|
||||||
draw(settings.drawMode);
|
draw(settings.drawMode);
|
||||||
interval=setInterval(draw, REFRESH_RATE, settings.drawMode);
|
interval=setInterval(draw, REFRESH_RATE, settings.drawMode);
|
||||||
}else
|
}else
|
||||||
|
@ -90,4 +90,4 @@ Bangle.on('lcdPower', function(on) {
|
||||||
});
|
});
|
||||||
|
|
||||||
Bangle.loadWidgets();
|
Bangle.loadWidgets();
|
||||||
Bangle.drawWidgets();
|
Bangle.drawWidgets();
|
||||||
|
|
|
@ -12,4 +12,8 @@
|
||||||
0.12: Fix memory leak (#206)
|
0.12: Fix memory leak (#206)
|
||||||
Bring App settings nearer the top
|
Bring App settings nearer the top
|
||||||
Move LCD Timeout to wakeup menu
|
Move LCD Timeout to wakeup menu
|
||||||
0.13: Move LCD Brightness menu into more general LCD menu
|
0.13: Fix memory leak for App settings
|
||||||
|
Make capitalization more consistent
|
||||||
|
Move LCD Brightness menu into more general LCD menu
|
||||||
|
0.14: Reduce memory usage when running app settings page
|
||||||
|
0.15: Reduce memory usage when running default clock chooser (#294)
|
||||||
|
|
|
@ -64,7 +64,7 @@ function showMainMenu() {
|
||||||
const mainmenu = {
|
const mainmenu = {
|
||||||
'': { 'title': 'Settings' },
|
'': { 'title': 'Settings' },
|
||||||
'Make Connectable': ()=>makeConnectable(),
|
'Make Connectable': ()=>makeConnectable(),
|
||||||
'App/widget settings': ()=>showAppSettingsMenu(),
|
'App/Widget Settings': ()=>showAppSettingsMenu(),
|
||||||
'BLE': {
|
'BLE': {
|
||||||
value: settings.ble,
|
value: settings.ble,
|
||||||
format: boolFormat,
|
format: boolFormat,
|
||||||
|
@ -81,7 +81,7 @@ function showMainMenu() {
|
||||||
updateSettings();
|
updateSettings();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'Debug info': {
|
'Debug Info': {
|
||||||
value: settings.log,
|
value: settings.log,
|
||||||
format: v => v ? "Show" : "Hide",
|
format: v => v ? "Show" : "Hide",
|
||||||
onchange: () => {
|
onchange: () => {
|
||||||
|
@ -296,10 +296,10 @@ function makeConnectable() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function showClockMenu() {
|
function showClockMenu() {
|
||||||
var clockApps = require("Storage").list(/\.info$/).map(app => {
|
var clockApps = require("Storage").list(/\.info$/)
|
||||||
try { return require("Storage").readJSON(app); }
|
.map(app => {var a=storage.readJSON(app, 1);return (a&&a.type == "clock")?a:undefined})
|
||||||
catch (e) { }
|
.filter(app => app) // filter out any undefined apps
|
||||||
}).filter(app => app.type == "clock").sort((a, b) => a.sortorder - b.sortorder);
|
.sort((a, b) => a.sortorder - b.sortorder);
|
||||||
const clockMenu = {
|
const clockMenu = {
|
||||||
'': {
|
'': {
|
||||||
'title': 'Select Clock',
|
'title': 'Select Clock',
|
||||||
|
@ -325,8 +325,6 @@ function showClockMenu() {
|
||||||
return E.showMenu(clockMenu);
|
return E.showMenu(clockMenu);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function showSetTimeMenu() {
|
function showSetTimeMenu() {
|
||||||
d = new Date();
|
d = new Date();
|
||||||
const timemenu = {
|
const timemenu = {
|
||||||
|
@ -419,8 +417,8 @@ function showAppSettingsMenu() {
|
||||||
'< Back': ()=>showMainMenu(),
|
'< Back': ()=>showMainMenu(),
|
||||||
}
|
}
|
||||||
const apps = storage.list(/\.info$/)
|
const apps = storage.list(/\.info$/)
|
||||||
.map(app => storage.readJSON(app, 1))
|
.map(app => {var a=storage.readJSON(app, 1);return (a&&a.settings)?a:undefined})
|
||||||
.filter(app => app && app.settings)
|
.filter(app => app) // filter out any undefined apps
|
||||||
.sort((a, b) => a.sortorder - b.sortorder)
|
.sort((a, b) => a.sortorder - b.sortorder)
|
||||||
if (apps.length === 0) {
|
if (apps.length === 0) {
|
||||||
appmenu['No app has settings'] = () => { };
|
appmenu['No app has settings'] = () => { };
|
||||||
|
@ -450,7 +448,7 @@ function showAppSettings(app) {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// pass showAppSettingsMenu as "back" argument
|
// pass showAppSettingsMenu as "back" argument
|
||||||
appSettings(showAppSettingsMenu);
|
appSettings(()=>showAppSettingsMenu());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(`${app.name} settings error:`, e)
|
console.log(`${app.name} settings error:`, e)
|
||||||
return showError('Error in settings');
|
return showError('Error in settings');
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
0.01: New App!
|
||||||
|
0.02: Performance and graphic improvements, game pause, beep and buzz
|
|
@ -0,0 +1,14 @@
|
||||||
|
# Snake
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
The legentary classic game is now available on Bangle.js!
|
||||||
|
Eat apples and don't bite your tail.
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
|
||||||
|
- UP: BTN1
|
||||||
|
- DOWN: BTN3
|
||||||
|
- LEFT: BTN4
|
||||||
|
- RIGHT: BTN5
|
||||||
|
- PAUSE: BTN2
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AH4A/ADE3m9hsIusrdhGIM3LtU3g0GAgQxlEwIqBmEAgEGF4QwkF4c3F4MxF4dbF8qLDrYHDre74IABF8QwBLoaPDF8wPKF96/jF/4v/F/4vrrc3AIQsnsIAKF94wiFxgv/R//+m4ABrYALBwIpYFwwAQLC4v/F7gXGF91hACovWFqwwUF4VbF7IwUFzSRVF1gwCF9wwZFyoA/AH4A/AH4A/AGg="))
|
|
@ -0,0 +1,155 @@
|
||||||
|
Bangle.setLCDMode("120x120");
|
||||||
|
|
||||||
|
const H = g.getWidth();
|
||||||
|
const W = g.getHeight();
|
||||||
|
let running = true;
|
||||||
|
let score = 0;
|
||||||
|
let d;
|
||||||
|
const gridSize = 20;
|
||||||
|
const tileSize = 6;
|
||||||
|
let nextX = 0;
|
||||||
|
let nextY = 0;
|
||||||
|
const defaultTailSize = 3;
|
||||||
|
let tailSize = defaultTailSize;
|
||||||
|
const snakeTrail = [];
|
||||||
|
const snake = { x: 10, y: 10 };
|
||||||
|
const apple = { x: Math.floor(Math.random() * gridSize), y: Math.floor(Math.random() * gridSize) };
|
||||||
|
|
||||||
|
function drawBackground(){
|
||||||
|
g.setColor("#000000");
|
||||||
|
g.fillRect(0, 0, H, W);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawApple(){
|
||||||
|
g.setColor("#FF0000");
|
||||||
|
g.fillCircle((apple.x * tileSize) + tileSize/2, (apple.y * tileSize) + tileSize/2, tileSize/2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawSnake(){
|
||||||
|
g.setColor("#008000");
|
||||||
|
for (let i = 0; i < snakeTrail.length; i++) {
|
||||||
|
g.fillRect(snakeTrail[i].x * tileSize, snakeTrail[i].y * tileSize, snakeTrail[i].x * tileSize + tileSize, snakeTrail[i].y * tileSize + tileSize);
|
||||||
|
|
||||||
|
//snake bites it's tail
|
||||||
|
if (snakeTrail[i].x === snake.x && snakeTrail[i].y === snake.y && tailSize > defaultTailSize) {
|
||||||
|
Bangle.buzz(1000);
|
||||||
|
gameOver();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawScore(){
|
||||||
|
g.setColor("#FFFFFF");
|
||||||
|
g.setFont("6x8");
|
||||||
|
g.setFontAlign(0, 0);
|
||||||
|
g.drawString("Score:" + score, W / 2, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
function gameStart() {
|
||||||
|
running = true;
|
||||||
|
score = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function gameOver() {
|
||||||
|
g.clear();
|
||||||
|
g.setColor("#FFFFFF");
|
||||||
|
g.setFont("6x8");
|
||||||
|
g.drawString("GAME OVER!", W / 2, H / 2 - 10);
|
||||||
|
g.drawString("Tap to Restart", W / 2, H / 2 + 10);
|
||||||
|
running = false;
|
||||||
|
tailSize = defaultTailSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
function draw() {
|
||||||
|
if (!running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.clear();
|
||||||
|
|
||||||
|
// move snake in next pos
|
||||||
|
snake.x += nextX;
|
||||||
|
snake.y += nextY;
|
||||||
|
|
||||||
|
// snake over game world
|
||||||
|
if (snake.x < 0) {
|
||||||
|
snake.x = gridSize - 1;
|
||||||
|
}
|
||||||
|
if (snake.x > gridSize - 1) {
|
||||||
|
snake.x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (snake.y < 0) {
|
||||||
|
snake.y = gridSize - 1;
|
||||||
|
}
|
||||||
|
if (snake.y > gridSize - 1) {
|
||||||
|
snake.y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//snake bite apple
|
||||||
|
if (snake.x === apple.x && snake.y === apple.y) {
|
||||||
|
Bangle.beep(20);
|
||||||
|
tailSize++;
|
||||||
|
score++;
|
||||||
|
|
||||||
|
apple.x = Math.floor(Math.random() * gridSize);
|
||||||
|
apple.y = Math.floor(Math.random() * gridSize);
|
||||||
|
drawApple();
|
||||||
|
}
|
||||||
|
|
||||||
|
drawBackground();
|
||||||
|
drawApple();
|
||||||
|
drawSnake();
|
||||||
|
drawScore();
|
||||||
|
|
||||||
|
//set snake trail
|
||||||
|
snakeTrail.push({ x: snake.x, y: snake.y });
|
||||||
|
while (snakeTrail.length > tailSize) {
|
||||||
|
snakeTrail.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
g.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
// input
|
||||||
|
setWatch(() => {// Up
|
||||||
|
if (d !== 'd') {
|
||||||
|
nextX = 0;
|
||||||
|
nextY = -1;
|
||||||
|
d = 'u';
|
||||||
|
}
|
||||||
|
}, BTN1, { repeat: true });
|
||||||
|
setWatch(() => {// Down
|
||||||
|
if (d !== 'u') {
|
||||||
|
nextX = 0;
|
||||||
|
nextY = 1;
|
||||||
|
d = 'd';
|
||||||
|
}
|
||||||
|
}, BTN3, { repeat: true });
|
||||||
|
setWatch(() => {// Left
|
||||||
|
if (d !== 'r') {
|
||||||
|
nextX = -1;
|
||||||
|
nextY = 0;
|
||||||
|
d = 'l';
|
||||||
|
}
|
||||||
|
}, BTN4, { repeat: true });
|
||||||
|
setWatch(() => {// Right
|
||||||
|
if (d !== 'l') {
|
||||||
|
nextX = 1;
|
||||||
|
nextY = 0;
|
||||||
|
d = 'r';
|
||||||
|
}
|
||||||
|
}, BTN5, { repeat: true });
|
||||||
|
setWatch(() => {// Pause
|
||||||
|
running = !running;
|
||||||
|
}, BTN2, { repeat: true });
|
||||||
|
|
||||||
|
Bangle.on('touch', button => {
|
||||||
|
if (!running) {
|
||||||
|
gameStart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// render X times per second
|
||||||
|
const x = 5;
|
||||||
|
setInterval(draw, 1000 / x);
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -4,3 +4,6 @@
|
||||||
0.04: Fix regression after tweaks to Storage.readJSON
|
0.04: Fix regression after tweaks to Storage.readJSON
|
||||||
0.05: Move configuration into App/widget settings
|
0.05: Move configuration into App/widget settings
|
||||||
0.06: Move loader into welcome.boot.js
|
0.06: Move loader into welcome.boot.js
|
||||||
|
0.07: Run again when updated
|
||||||
|
Don't run again when settings app is updated (or absent)
|
||||||
|
Add "Run Now" option to settings
|
||||||
|
|
|
@ -288,6 +288,13 @@ setWatch(()=>{
|
||||||
}, BTN2, {repeat:true,edge:"rising"});
|
}, BTN2, {repeat:true,edge:"rising"});
|
||||||
setWatch(()=>move(-1), BTN1, {repeat:true});
|
setWatch(()=>move(-1), BTN1, {repeat:true});
|
||||||
|
|
||||||
|
(function migrateSettings(){
|
||||||
|
let global_settings = require('Storage').readJSON('setting.json', 1)
|
||||||
|
if (global_settings) {
|
||||||
|
delete global_settings.welcomed
|
||||||
|
require('Storage').write('setting.json', global_settings)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
|
||||||
Bangle.setLCDTimeout(0);
|
Bangle.setLCDTimeout(0);
|
||||||
Bangle.setLCDPower(1);
|
Bangle.setLCDPower(1);
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
(function() {
|
(function() {
|
||||||
let s = require('Storage').readJSON('setting.json', 1) || {}
|
let s = require('Storage').readJSON('welcome.settings.json', 1)
|
||||||
|
|| require('Storage').readJSON('setting.json', 1)
|
||||||
|
|| {welcomed: true} // do NOT run if global settings are also absent
|
||||||
if (!s.welcomed && require('Storage').read('welcome.app.js')) {
|
if (!s.welcomed && require('Storage').read('welcome.app.js')) {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
s.welcomed = true
|
s.welcomed = true
|
||||||
require('Storage').write('setting.json', s)
|
require('Storage').write('welcome.settings.json', {welcomed: "yes"})
|
||||||
load('welcome.app.js')
|
load('welcome.app.js')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"welcomed": false
|
||||||
|
}
|
|
@ -1,16 +1,15 @@
|
||||||
// The welcome app is special, and gets to use global settings
|
// The welcome app is special, and gets to use global settings
|
||||||
(function(back) {
|
(function(back) {
|
||||||
let settings = require('Storage').readJSON('setting.json', 1) || {}
|
let settings = require('Storage').readJSON('welcome.settings.json', 1)
|
||||||
|
|| require('Storage').readJSON('setting.json', 1) || {}
|
||||||
E.showMenu({
|
E.showMenu({
|
||||||
'': { 'title': 'Welcome App' },
|
'': { 'title': 'Welcome App' },
|
||||||
'Run again': {
|
'Run on Next Boot': {
|
||||||
value: !settings.welcomed,
|
value: !settings.welcomed,
|
||||||
format: v => v ? 'Yes' : 'No',
|
format: v => v ? 'OK' : 'No',
|
||||||
onchange: v => {
|
onchange: v => require('Storage').write('welcome.settings.json', {welcomed: !v}),
|
||||||
settings.welcomed = v ? undefined : true
|
|
||||||
require('Storage').write('setting.json', settings)
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
'Run Now': () => load('welcome.app.js'),
|
||||||
'< Back': back,
|
'< Back': back,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
0.02: Now refresh battery monitor every minute if LCD on
|
0.02: Now refresh battery monitor every minute if LCD on
|
||||||
0.03: Tweaks for variable size widget system
|
0.03: Tweaks for variable size widget system
|
||||||
0.04: Ensure redrawing works with variable size widget system
|
0.04: Ensure redrawing works with variable size widget system
|
||||||
|
0.05: Fix regression stopping correct widget updates
|
||||||
|
|
|
@ -30,7 +30,7 @@ Bangle.on('lcdPower', function(on) {
|
||||||
WIDGETS["bat"].draw();
|
WIDGETS["bat"].draw();
|
||||||
// refresh once a minute if LCD on
|
// refresh once a minute if LCD on
|
||||||
if (!batteryInterval)
|
if (!batteryInterval)
|
||||||
batteryInterval = setInterval(draw, 60000);
|
batteryInterval = setInterval(()=>WIDGETS["bat"].draw(), 60000);
|
||||||
} else {
|
} else {
|
||||||
if (batteryInterval) {
|
if (batteryInterval) {
|
||||||
clearInterval(batteryInterval);
|
clearInterval(batteryInterval);
|
||||||
|
|
|
@ -5,3 +5,4 @@
|
||||||
0.06: Show battery percentage as text
|
0.06: Show battery percentage as text
|
||||||
0.07: Add settings: percentage/color/charger icon
|
0.07: Add settings: percentage/color/charger icon
|
||||||
0.08: Draw percentage as inverted on monochrome battery
|
0.08: Draw percentage as inverted on monochrome battery
|
||||||
|
0.09: Fix regression stopping correct widget updates
|
||||||
|
|
|
@ -110,7 +110,7 @@ Bangle.on('lcdPower', function(on) {
|
||||||
WIDGETS["batpc"].draw();
|
WIDGETS["batpc"].draw();
|
||||||
// refresh once a minute if LCD on
|
// refresh once a minute if LCD on
|
||||||
if (!batteryInterval)
|
if (!batteryInterval)
|
||||||
batteryInterval = setInterval(draw, 60000);
|
batteryInterval = setInterval(()=>WIDGETS["batpc"].draw(), 60000);
|
||||||
} else {
|
} else {
|
||||||
if (batteryInterval) {
|
if (batteryInterval) {
|
||||||
clearInterval(batteryInterval);
|
clearInterval(batteryInterval);
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.02: Tweaks for variable size widget system
|
0.02: Tweaks for variable size widget system
|
||||||
0.03: Ensure redrawing works with variable size widget system
|
0.03: Ensure redrawing works with variable size widget system
|
||||||
|
0.04: Fix automatic update of Bluetooth connection status
|
||||||
|
|
|
@ -13,7 +13,7 @@ function changed() {
|
||||||
WIDGETS["bluetooth"].draw();
|
WIDGETS["bluetooth"].draw();
|
||||||
g.flip();// turns screen on
|
g.flip();// turns screen on
|
||||||
}
|
}
|
||||||
NRF.on('connected',changed);
|
NRF.on('connect',changed);
|
||||||
NRF.on('disconnected',changed);
|
NRF.on('disconnect',changed);
|
||||||
WIDGETS["bluetooth"]={area:"tr",width:24,draw:draw};
|
WIDGETS["bluetooth"]={area:"tr",width:24,draw:draw};
|
||||||
})()
|
})()
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
0.02: Now refresh battery monitor every minute if LCD on
|
0.02: Now refresh battery monitor every minute if LCD on
|
||||||
0.03: Ensure redrawing works with variable size widget system
|
0.03: Ensure redrawing works with variable size widget system
|
||||||
|
0.04: Fix regression stopping correct widget updates
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function startTimers(){
|
function startTimers(){
|
||||||
intervalRef = setInterval(draw, 60*1000);
|
intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000);
|
||||||
WIDGETS["wdclk"].draw();
|
WIDGETS["wdclk"].draw();
|
||||||
}
|
}
|
||||||
Bangle.on('lcdPower', (on) => {
|
Bangle.on('lcdPower', (on) => {
|
||||||
|
@ -23,5 +23,5 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
WIDGETS["wdclk"]={area:"tr",width:width,draw:draw};
|
WIDGETS["wdclk"]={area:"tr",width:width,draw:draw};
|
||||||
if (Bangle.isLCDOn) intervalRef = setInterval(draw, 60*1000);
|
if (Bangle.isLCDOn) intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000);
|
||||||
})()
|
})()
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
0.01: New Widget!
|
|
@ -0,0 +1,23 @@
|
||||||
|
(() => {
|
||||||
|
function draw() {
|
||||||
|
g.reset();
|
||||||
|
var m = process.memory();
|
||||||
|
var pc = Math.round(m.usage*100/m.total);
|
||||||
|
g.drawImage(atob("BwgBqgP////AVQ=="), this.x+(24-7)/2, this.y+4);
|
||||||
|
g.setColor(pc>70 ? "#ff0000" : (pc>50 ? "#ffff00" : "#ffffff"));
|
||||||
|
g.setFont("6x8").setFontAlign(0,0).drawString(pc+"%", this.x+12, this.y+20, true/*solid*/);
|
||||||
|
}
|
||||||
|
var ramInterval;
|
||||||
|
Bangle.on('lcdPower', function(on) {
|
||||||
|
if (on) {
|
||||||
|
WIDGETS["ram"].draw();
|
||||||
|
if (!ramInterval) ramInterval = setInterval(()=>WIDGETS["ram"].draw(), 10000);
|
||||||
|
} else {
|
||||||
|
if (ramInterval) {
|
||||||
|
clearInterval(ramInterval);
|
||||||
|
ramInterval = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
WIDGETS["ram"]={area:"tl",width: 24,draw:draw};
|
||||||
|
})()
|
After Width: | Height: | Size: 403 B |
|
@ -37,7 +37,13 @@ try{
|
||||||
ERROR("apps.json not valid JSON");
|
ERROR("apps.json not valid JSON");
|
||||||
}
|
}
|
||||||
|
|
||||||
apps.forEach((app,addIdx) => {
|
const APP_KEYS = [
|
||||||
|
'id', 'name', 'shortName', 'version', 'icon', 'description', 'tags', 'type',
|
||||||
|
'sortorder', 'readme', 'custom', 'interface', 'storage', 'allow_emulator',
|
||||||
|
];
|
||||||
|
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate'];
|
||||||
|
|
||||||
|
apps.forEach((app,appIdx) => {
|
||||||
if (!app.id) ERROR(`App ${appIdx} has no id`);
|
if (!app.id) ERROR(`App ${appIdx} has no id`);
|
||||||
//console.log(`Checking ${app.id}...`);
|
//console.log(`Checking ${app.id}...`);
|
||||||
var appDir = APPSDIR+app.id+"/";
|
var appDir = APPSDIR+app.id+"/";
|
||||||
|
@ -105,9 +111,15 @@ apps.forEach((app,addIdx) => {
|
||||||
ERROR(`App ${app.id}'s ${file.name} is a JS file but isn't valid JS`);
|
ERROR(`App ${app.id}'s ${file.name} is a JS file but isn't valid JS`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (const key in file) {
|
||||||
|
if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id}'s ${file.name} has unknown key ${key}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
//console.log(fileNames);
|
//console.log(fileNames);
|
||||||
if (isApp && !fileNames.includes(app.id+".app.js")) ERROR(`App ${app.id} has no entrypoint`);
|
if (isApp && !fileNames.includes(app.id+".app.js")) ERROR(`App ${app.id} has no entrypoint`);
|
||||||
if (isApp && !fileNames.includes(app.id+".img")) ERROR(`App ${app.id} has no JS icon`);
|
if (isApp && !fileNames.includes(app.id+".img")) ERROR(`App ${app.id} has no JS icon`);
|
||||||
if (app.type=="widget" && !fileNames.includes(app.id+".wid.js")) ERROR(`Widget ${app.id} has no entrypoint`);
|
if (app.type=="widget" && !fileNames.includes(app.id+".wid.js")) ERROR(`Widget ${app.id} has no entrypoint`);
|
||||||
|
for (const key in app) {
|
||||||
|
if (!APP_KEYS.includes(key)) ERROR(`App ${app.id} has unknown key ${key}`);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
15
js/comms.js
|
@ -40,7 +40,7 @@ uploadApp : (app,skipReset) => { // expects an apps.json structure (i.e. with `s
|
||||||
currentBytes += f.content.length;
|
currentBytes += f.content.length;
|
||||||
// Chould check CRC here if needed instead of returning 'OK'...
|
// Chould check CRC here if needed instead of returning 'OK'...
|
||||||
// E.CRC32(require("Storage").read(${JSON.stringify(app.name)}))
|
// E.CRC32(require("Storage").read(${JSON.stringify(app.name)}))
|
||||||
Puck.write(`\x10${f.cmd};Bluetooth.println("OK")\n`,(result) => {
|
Puck.write(`${f.cmd};Bluetooth.println("OK")\n`,(result) => {
|
||||||
if (!result || result.trim()!="OK") {
|
if (!result || result.trim()!="OK") {
|
||||||
Progress.hide({sticky:true});
|
Progress.hide({sticky:true});
|
||||||
return reject("Unexpected response "+(result||""));
|
return reject("Unexpected response "+(result||""));
|
||||||
|
@ -75,12 +75,21 @@ getInstalledApps : () => {
|
||||||
Progress.hide({sticky:true});
|
Progress.hide({sticky:true});
|
||||||
return reject("");
|
return reject("");
|
||||||
}
|
}
|
||||||
Puck.eval('require("Storage").list(/\.info$/).map(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);return j})', (appList,err) => {
|
Puck.write('\x10Bluetooth.print("[");require("Storage").list(/\.info$/).forEach(f=>{var j=require("Storage").readJSON(f,1)||{};j.id=f.slice(0,-5);Bluetooth.print(JSON.stringify(j)+",")});Bluetooth.println("0]")\n', (appList,err) => {
|
||||||
Progress.hide({sticky:true});
|
Progress.hide({sticky:true});
|
||||||
|
try {
|
||||||
|
appList = JSON.parse(appList);
|
||||||
|
// remove last element since we added a final '0'
|
||||||
|
// to make things easy on the Bangle.js side
|
||||||
|
appList = appList.slice(0,-1);
|
||||||
|
} catch (e) {
|
||||||
|
appList = null;
|
||||||
|
err = e.toString();
|
||||||
|
}
|
||||||
if (appList===null) return reject(err || "");
|
if (appList===null) return reject(err || "");
|
||||||
console.log("getInstalledApps", appList);
|
console.log("getInstalledApps", appList);
|
||||||
resolve(appList);
|
resolve(appList);
|
||||||
});
|
}, true /* callback on newline */);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -56,7 +56,7 @@ function getVersionInfo(appListing, appInstalled) {
|
||||||
if (appListing.version)
|
if (appListing.version)
|
||||||
versionText = clicky("v"+appListing.version);
|
versionText = clicky("v"+appListing.version);
|
||||||
} else {
|
} else {
|
||||||
versionText = (appInstalled.version ? ("v"+appInstalled.version) : "Unknown version");
|
versionText = (appInstalled.version ? (clicky("v"+appInstalled.version)) : "Unknown version");
|
||||||
if (appListing.version != appInstalled.version) {
|
if (appListing.version != appInstalled.version) {
|
||||||
if (appListing.version) versionText += ", latest "+clicky("v"+appListing.version);
|
if (appListing.version) versionText += ", latest "+clicky("v"+appListing.version);
|
||||||
canUpdate = true;
|
canUpdate = true;
|
||||||
|
|