|
@ -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
|
||||
* Added optional `README.md` file for apps
|
||||
* 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
|
||||
* Version number now clickable even when you're at the latest version (fix #291)
|
||||
* Rewrite 'getInstalledApps' to minimize RAM usage
|
||||
|
|
93
apps.json
|
@ -78,7 +78,7 @@
|
|||
{ "id": "welcome",
|
||||
"name": "Welcome",
|
||||
"icon": "app.png",
|
||||
"version":"0.06",
|
||||
"version":"0.07",
|
||||
"description": "Appears at first boot and explains how to use Bangle.js",
|
||||
"tags": "start,welcome",
|
||||
"allow_emulator":true,
|
||||
|
@ -86,13 +86,14 @@
|
|||
{"name":"welcome.boot.js","url":"boot.js"},
|
||||
{"name":"welcome.app.js","url":"app.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}
|
||||
]
|
||||
},
|
||||
{ "id": "gbridge",
|
||||
"name": "Gadgetbridge",
|
||||
"icon": "app.png",
|
||||
"version":"0.08",
|
||||
"version":"0.09",
|
||||
"description": "The default notification handler for Gadgetbridge notifications from Android",
|
||||
"tags": "tool,system,android,widget",
|
||||
"type":"widget",
|
||||
|
@ -119,7 +120,7 @@
|
|||
{ "id": "setting",
|
||||
"name": "Settings",
|
||||
"icon": "settings.png",
|
||||
"version":"0.13",
|
||||
"version":"0.15",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"tags": "tool,system",
|
||||
"storage": [
|
||||
|
@ -279,7 +280,7 @@
|
|||
{ "id": "gpsrec",
|
||||
"name": "GPS Recorder",
|
||||
"icon": "app.png",
|
||||
"version":"0.06",
|
||||
"version":"0.07",
|
||||
"interface": "interface.html",
|
||||
"description": "Application that allows you to record a GPS track. Can run in background",
|
||||
"tags": "tool,outdoors,gps,widget",
|
||||
|
@ -329,7 +330,7 @@
|
|||
{ "id": "widbat",
|
||||
"name": "Battery Level Widget",
|
||||
"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",
|
||||
"tags": "widget,battery",
|
||||
"type":"widget",
|
||||
|
@ -341,7 +342,7 @@
|
|||
"name": "Battery Level Widget (with percentage)",
|
||||
"shortName": "Battery Widget",
|
||||
"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",
|
||||
"tags": "widget,battery",
|
||||
"type":"widget",
|
||||
|
@ -354,7 +355,7 @@
|
|||
{ "id": "widbt",
|
||||
"name": "Bluetooth Widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"description": "Show the current Bluetooth connection status in the top right of the clock",
|
||||
"tags": "widget,bluetooth",
|
||||
"type":"widget",
|
||||
|
@ -362,6 +363,18 @@
|
|||
{"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",
|
||||
"name": "Heart Rate Monitor",
|
||||
"icon": "heartrate.png",
|
||||
|
@ -504,13 +517,14 @@
|
|||
"id": "ncstart",
|
||||
"name": "NCEU Startup",
|
||||
"icon": "start.png",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"description": "NodeConfEU 2019 'First Start' Sequence",
|
||||
"tags": "start,welcome",
|
||||
"storage": [
|
||||
{"name":"ncstart.app.js","url":"start.js"},
|
||||
{"name":"ncstart.boot.js","url":"boot.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":"nc-bangle.img","url":"start-bangle.js","evaluate":true},
|
||||
{"name":"nc-nceu.img","url":"start-nceu.js","evaluate":true},
|
||||
|
@ -775,7 +789,7 @@
|
|||
{ "id": "widclk",
|
||||
"name": "Digital clock widget",
|
||||
"icon": "widget.png",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"description": "A simple digital clock widget",
|
||||
"tags": "widget,clock",
|
||||
"type":"widget",
|
||||
|
@ -915,7 +929,7 @@
|
|||
{ "id": "marioclock",
|
||||
"name": "Mario Clock",
|
||||
"icon": "marioclock.png",
|
||||
"version":"0.09",
|
||||
"version":"0.12",
|
||||
"description": "Animated retro Mario clock, with Gameboy style 8-bit grey-scale graphics.",
|
||||
"tags": "clock,mario,retro",
|
||||
"type": "clock",
|
||||
|
@ -1108,6 +1122,20 @@
|
|||
{"name":"openstmap.app.js","url":"app.js"},
|
||||
{"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",
|
||||
"name": "Tabata",
|
||||
|
@ -1148,9 +1176,9 @@
|
|||
},
|
||||
{ "id": "batchart",
|
||||
"name": "Battery Chart",
|
||||
"shortName":"BatChart",
|
||||
"shortName":"Battery Chart",
|
||||
"icon": "app.png",
|
||||
"version":"0.03",
|
||||
"version":"0.07",
|
||||
"description": "A widget and an app for recording and visualizing battery percentage over time.",
|
||||
"tags": "app,widget,battery,time,record,chart,tool",
|
||||
"storage": [
|
||||
|
@ -1159,11 +1187,25 @@
|
|||
{"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",
|
||||
"name": "Numerals Clock",
|
||||
"shortName": "Numerals Clock",
|
||||
"icon": "numerals.png",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "A simple big numerals clock",
|
||||
"tags": "numerals,clock",
|
||||
"type":"clock",
|
||||
|
@ -1186,5 +1228,30 @@
|
|||
{"name":"bledetect.app.js","url":"bledetect.js"},
|
||||
{"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.02: Widget stores data to file (1 dataset/10min)
|
||||
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(){
|
||||
g.drawString("t", 215, 175);
|
||||
g.drawLine(40,190,40,80);
|
||||
const GraphMarkerOffset = 5;
|
||||
const MaxValueCount = 144;
|
||||
const GraphXMax = GraphXZero + MaxValueCount;
|
||||
|
||||
g.drawString("%", 39, 70);
|
||||
g.drawString("100", 15, 75);
|
||||
g.drawLine(35,80,40,80);
|
||||
const GraphLcdY = GraphYZero + 10;
|
||||
const GraphCompassY = GraphYZero + 16;
|
||||
// const GraphBluetoothY = GraphYZero + 22;
|
||||
const GraphGpsY = GraphYZero + 28;
|
||||
const GraphHrmY = GraphYZero + 34;
|
||||
|
||||
g.drawString("50", 20,125);
|
||||
g.drawLine(35,130,40,130);
|
||||
const Storage = require("Storage");
|
||||
|
||||
g.drawString("0", 25, 175);
|
||||
g.drawLine(35,180,210,180);
|
||||
function renderCoordinateSystem() {
|
||||
g.setFont("6x8", 1);
|
||||
|
||||
g.drawString("Chart not yet functional", 60, 125);
|
||||
// Left Y axis (Battery)
|
||||
g.setColor(1, 1, 0);
|
||||
g.drawLine(GraphXZero, GraphYZero + GraphMarkerOffset, GraphXZero, GraphY100);
|
||||
g.drawString("%", 39, GraphY100 - 10);
|
||||
|
||||
g.setFontAlign(1, -1, 0);
|
||||
g.drawString("100", 30, GraphY100 - GraphMarkerOffset);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, GraphY100, GraphXZero, GraphY100);
|
||||
|
||||
g.drawString("50", 30, GraphYZero - 50 - GraphMarkerOffset);
|
||||
g.drawLine(GraphXZero - GraphMarkerOffset, 130, GraphXZero, 130);
|
||||
|
||||
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
|
||||
|
@ -22,13 +215,20 @@ Bangle.on('lcdPower', (on) => {
|
|||
if (on) {
|
||||
// call your app function here
|
||||
// If you clear the screen, do Bangle.drawWidgets();
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
renderBatteryChart();
|
||||
}
|
||||
});
|
||||
|
||||
setWatch(switchOffApp, BTN2, {edge:"rising", debounce:50, repeat:true});
|
||||
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
// call your app function here
|
||||
|
||||
renderHomeIcon();
|
||||
|
||||
renderBatteryChart();
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
(() => {
|
||||
var switchableConsumers = {
|
||||
const Storage = require("Storage");
|
||||
|
||||
const switchableConsumers = {
|
||||
none: 0,
|
||||
lcd: 1,
|
||||
compass: 2,
|
||||
|
@ -8,59 +10,106 @@
|
|||
hrm: 16
|
||||
};
|
||||
|
||||
var settings = {};
|
||||
var batChartFile; // file for battery percentage recording
|
||||
const recordingInterval10Min = 60 * 10 * 1000;
|
||||
const recordingInterval1Min = 60*1000; //For testing
|
||||
const recordingInterval10S = 10*1000; //For testing
|
||||
var recordingInterval = null;
|
||||
|
||||
var compassEventReceived = false;
|
||||
var gpsEventReceived = false;
|
||||
var hrmEventReceived = false;
|
||||
|
||||
// draw your widget
|
||||
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.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() {
|
||||
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())
|
||||
enabledConsumers = enabledConsumers | switchableConsumers.lcd;
|
||||
// Already added in the hope they will be available soon to get more details
|
||||
// if (Bangle.isCompassOn())
|
||||
// enabledConsumers = enabledConsumers | switchableConsumers.compass;
|
||||
// if (Bangle.isBluetoothOn())
|
||||
if (compassEventReceived)
|
||||
enabledConsumers = enabledConsumers | switchableConsumers.compass;
|
||||
if (gpsEventReceived)
|
||||
enabledConsumers = enabledConsumers | switchableConsumers.gps;
|
||||
if (hrmEventReceived)
|
||||
enabledConsumers = enabledConsumers | switchableConsumers.hrm;
|
||||
//if (Bangle.isBluetoothOn())
|
||||
// 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() {
|
||||
const previousWriteLogName = "bcprvday";
|
||||
const previousWriteDay = require("Storage").read(previousWriteLogName);
|
||||
const previousWriteDay = parseInt(Storage.open(previousWriteLogName, "r").readLine());
|
||||
const currentWriteDay = new Date().getDay();
|
||||
|
||||
const logFileName = "bclog" + currentWriteDay;
|
||||
|
||||
// Change log target on day change
|
||||
if (previousWriteDay != currentWriteDay) {
|
||||
if (!isNaN(previousWriteDay)
|
||||
&& previousWriteDay != currentWriteDay) {
|
||||
//Remove a log file containing data from a week ago
|
||||
require("Storage").erase(logFileName);
|
||||
require("Storage").write(previousWriteLogName, currentWriteDay);
|
||||
Storage.open(logFileName, "r").erase();
|
||||
Storage.open(previousWriteLogName, "w").write(parseInt(currentWriteDay));
|
||||
}
|
||||
|
||||
var bcLogFileA = require("Storage").open(logFileName, "a");
|
||||
var bcLogFileA = Storage.open(logFileName, "a");
|
||||
if (bcLogFileA) {
|
||||
console.log([getTime().toFixed(0), E.getBattery(), E.getTemperature(), getEnabledConsumersValue()].join(","));
|
||||
bcLogFileA.write([[getTime().toFixed(0), E.getBattery(), E.getTemperature(), getEnabledConsumersValue()].join(",")].join(",")+"\n");
|
||||
let logTime = getTime().toFixed(0);
|
||||
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() {
|
||||
WIDGETS["batchart"].width = 24;
|
||||
|
||||
|
@ -74,6 +123,7 @@
|
|||
reload();
|
||||
Bangle.drawWidgets(); // relayout all widgets
|
||||
}};
|
||||
|
||||
// load settings, set correct widget width
|
||||
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.07: Move configuration to settings menu
|
||||
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
|
||||
}
|
||||
|
||||
NRF.on("connected", changedConnectionState);
|
||||
NRF.on("disconnected", changedConnectionState);
|
||||
NRF.on("connect", changedConnectionState);
|
||||
NRF.on("disconnect", changedConnectionState);
|
||||
|
||||
WIDGETS["gbridgew"] = { area: "tl", width: 24, draw: draw };
|
||||
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
0.04: Properly Fix GPS time display in gpsrec app
|
||||
0.05: Tweaks for variable size widget system
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
E.showMessage("Loading...","GPS Track "+n);
|
||||
var info = getTrackInfo(n);
|
||||
const menu = {
|
||||
'': { 'title': 'GPS Track '+n }
|
||||
};
|
||||
var trackCount = 0;
|
||||
var trackTime;
|
||||
var f = require("Storage").open(getFN(n),"r");
|
||||
var l = f.readLine();
|
||||
if (l!==undefined) {
|
||||
var c = l.split(",");
|
||||
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
|
||||
if (info.time)
|
||||
menu[info.time.toISOString().substr(0,16).replace("T"," ")] = function(){};
|
||||
menu["Duration"] = { value : asTime(info.duration)};
|
||||
menu["Records"] = { value : ""+info.records };
|
||||
menu['Plot'] = function() {
|
||||
plotTrack(info);
|
||||
};
|
||||
menu['Erase'] = function() {
|
||||
E.showPrompt("Delete Track?").then(function(v) {
|
||||
if (v) {
|
||||
|
@ -107,4 +145,80 @@ function viewTrack(n) {
|
|||
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();
|
||||
|
|
|
@ -7,3 +7,6 @@
|
|||
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.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
|
||||
|
||||
* 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
|
||||
* Awesome 8-bit style grey-scale graphics
|
||||
* Mario jumps to change the time, every minute
|
||||
|
|
|
@ -16,6 +16,8 @@ const is12Hour = settings["12hour"] || false;
|
|||
|
||||
// Screen dimensions
|
||||
let W, H;
|
||||
// Screen brightness
|
||||
let brightness = 1;
|
||||
|
||||
let intervalRef, displayTimeoutRef = null;
|
||||
|
||||
|
@ -79,6 +81,16 @@ const phone = {
|
|||
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) {
|
||||
Bluetooth.println(JSON.stringify(msg));
|
||||
}
|
||||
|
@ -164,7 +176,17 @@ function switchCharacter() {
|
|||
}
|
||||
|
||||
function toggleNightMode() {
|
||||
nightMode = !nightMode;
|
||||
if (!nightMode) {
|
||||
nightMode = true;
|
||||
return;
|
||||
}
|
||||
|
||||
brightness -= 0.30;
|
||||
if (brightness <= 0) {
|
||||
brightness = 1;
|
||||
nightMode = false;
|
||||
}
|
||||
Bangle.setLCDBrightness(brightness);
|
||||
}
|
||||
|
||||
function incrementTimer() {
|
||||
|
@ -324,16 +346,20 @@ function drawToadFrame(idx, x, y) {
|
|||
function drawNotice(x, y) {
|
||||
if (phone.message === null) return;
|
||||
|
||||
let img;
|
||||
switch (phone.messageType) {
|
||||
case "call":
|
||||
const callImg = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INEw9cAAIPFBxAPEBw/WBxYACDrQ7QLI53OSpApDBoQAHB4INLByANNAwo="));
|
||||
g.drawImage(callImg, characterSprite.x, characterSprite.y - 16);
|
||||
img = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INEw9cAAIPFBxAPEBw/WBxYACDrQ7QLI53OSpApDBoQAHB4INLByANNAwo="));
|
||||
break;
|
||||
case "notify":
|
||||
const msgImg = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INCrgAHB4QOEDQgOIAIQFGBwovDA4gOGFooOVLJR3OSpApDBoQAHB4INLByANNAwoA="));
|
||||
g.drawImage(msgImg, characterSprite.x, characterSprite.y - 16);
|
||||
img = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INCrgAHB4QOEDQgOIAIQFGBwovDA4gOGFooOVLJR3OSpApDBoQAHB4INLByANNAwoA="));
|
||||
break;
|
||||
case "lowBatt":
|
||||
img = require("heatshrink").decompress(atob("h8PxH+AAMHABIND6wAJB4INFrgABB4oOEBoQPFBwwDGB0uHAAIOLJRB3OSpApDBoQAHB4INLByANNAwo"));
|
||||
break;
|
||||
}
|
||||
|
||||
if (img) g.drawImage(img, characterSprite.x, characterSprite.y - 16);
|
||||
}
|
||||
|
||||
function drawCharacter(date, character) {
|
||||
|
@ -555,8 +581,39 @@ function startTimers(){
|
|||
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
|
||||
function init() {
|
||||
loadSettings();
|
||||
|
||||
clearInterval();
|
||||
|
||||
// Initialise display
|
||||
|
@ -606,23 +663,31 @@ function init() {
|
|||
default:
|
||||
toggleNightMode();
|
||||
}
|
||||
|
||||
updateSettings();
|
||||
});
|
||||
|
||||
// Phone connectivity
|
||||
try { NRF.wake(); } catch (e) {}
|
||||
|
||||
NRF.on('disconnect', () => Bangle.buzz());
|
||||
NRF.on('disconnect', () => {
|
||||
phoneNewMessage(null, "Phone disconnected");
|
||||
});
|
||||
|
||||
NRF.on('connect', () => {
|
||||
setTimeout(() => {
|
||||
phoneOutbound({ t: "status", bat: E.getBattery() });
|
||||
}, ONE_SECOND * 2);
|
||||
Bangle.buzz();
|
||||
phoneNewMessage(null, "Phone connected");
|
||||
});
|
||||
|
||||
GB = (evt) => phoneInbound(evt);
|
||||
|
||||
startTimers();
|
||||
|
||||
setInterval(checkBatteryLevel, ONE_SECOND * 60 * 10);
|
||||
checkBatteryLevel();
|
||||
}
|
||||
|
||||
// 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
|
||||
0.03: Move configuration into App/widget settings
|
||||
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() {
|
||||
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')) {
|
||||
setTimeout(() => {
|
||||
s.welcomed = true
|
||||
require('Storage').write('setting.json', s)
|
||||
require('Storage').write('ncstart.settings.json', s)
|
||||
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
|
||||
(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({
|
||||
'': { 'title': 'NCEU Startup' },
|
||||
'Run again': {
|
||||
'Run on Next Boot': {
|
||||
value: !settings.welcomed,
|
||||
format: v => v ? 'Yes' : 'No',
|
||||
onchange: v => {
|
||||
settings.welcomed = v ? undefined : true
|
||||
require('Storage').write('setting.json', settings)
|
||||
},
|
||||
format: v => v ? 'OK' : 'No',
|
||||
onchange: v => require('Storage').write('ncstart.settings.json', {welcomed: !v}),
|
||||
},
|
||||
'Run Now': () => load('ncstart.app.js'),
|
||||
'< Back': back,
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Use BTN2 for settings menu like other clocks
|
||||
|
|
|
@ -70,7 +70,7 @@ function draw(drawMode){
|
|||
Bangle.setLCDMode();
|
||||
|
||||
clearWatch();
|
||||
setWatch(Bangle.showLauncher, BTN1, {repeat:false,edge:"falling"});
|
||||
setWatch(Bangle.showLauncher, BTN2, {repeat:false,edge:"falling"});
|
||||
|
||||
g.clear();
|
||||
clearInterval();
|
||||
|
|
|
@ -12,4 +12,8 @@
|
|||
0.12: Fix memory leak (#206)
|
||||
Bring App settings nearer the top
|
||||
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 = {
|
||||
'': { 'title': 'Settings' },
|
||||
'Make Connectable': ()=>makeConnectable(),
|
||||
'App/widget settings': ()=>showAppSettingsMenu(),
|
||||
'App/Widget Settings': ()=>showAppSettingsMenu(),
|
||||
'BLE': {
|
||||
value: settings.ble,
|
||||
format: boolFormat,
|
||||
|
@ -81,7 +81,7 @@ function showMainMenu() {
|
|||
updateSettings();
|
||||
}
|
||||
},
|
||||
'Debug info': {
|
||||
'Debug Info': {
|
||||
value: settings.log,
|
||||
format: v => v ? "Show" : "Hide",
|
||||
onchange: () => {
|
||||
|
@ -296,10 +296,10 @@ function makeConnectable() {
|
|||
});
|
||||
}
|
||||
function showClockMenu() {
|
||||
var clockApps = require("Storage").list(/\.info$/).map(app => {
|
||||
try { return require("Storage").readJSON(app); }
|
||||
catch (e) { }
|
||||
}).filter(app => app.type == "clock").sort((a, b) => a.sortorder - b.sortorder);
|
||||
var clockApps = require("Storage").list(/\.info$/)
|
||||
.map(app => {var a=storage.readJSON(app, 1);return (a&&a.type == "clock")?a:undefined})
|
||||
.filter(app => app) // filter out any undefined apps
|
||||
.sort((a, b) => a.sortorder - b.sortorder);
|
||||
const clockMenu = {
|
||||
'': {
|
||||
'title': 'Select Clock',
|
||||
|
@ -325,8 +325,6 @@ function showClockMenu() {
|
|||
return E.showMenu(clockMenu);
|
||||
}
|
||||
|
||||
|
||||
|
||||
function showSetTimeMenu() {
|
||||
d = new Date();
|
||||
const timemenu = {
|
||||
|
@ -419,8 +417,8 @@ function showAppSettingsMenu() {
|
|||
'< Back': ()=>showMainMenu(),
|
||||
}
|
||||
const apps = storage.list(/\.info$/)
|
||||
.map(app => storage.readJSON(app, 1))
|
||||
.filter(app => app && app.settings)
|
||||
.map(app => {var a=storage.readJSON(app, 1);return (a&&a.settings)?a:undefined})
|
||||
.filter(app => app) // filter out any undefined apps
|
||||
.sort((a, b) => a.sortorder - b.sortorder)
|
||||
if (apps.length === 0) {
|
||||
appmenu['No app has settings'] = () => { };
|
||||
|
@ -450,7 +448,7 @@ function showAppSettings(app) {
|
|||
}
|
||||
try {
|
||||
// pass showAppSettingsMenu as "back" argument
|
||||
appSettings(showAppSettingsMenu);
|
||||
appSettings(()=>showAppSettingsMenu());
|
||||
} catch (e) {
|
||||
console.log(`${app.name} settings error:`, e)
|
||||
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.05: Move configuration into App/widget settings
|
||||
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"});
|
||||
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.setLCDPower(1);
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
(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')) {
|
||||
setTimeout(() => {
|
||||
s.welcomed = true
|
||||
require('Storage').write('setting.json', s)
|
||||
require('Storage').write('welcome.settings.json', {welcomed: "yes"})
|
||||
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
|
||||
(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({
|
||||
'': { 'title': 'Welcome App' },
|
||||
'Run again': {
|
||||
'Run on Next Boot': {
|
||||
value: !settings.welcomed,
|
||||
format: v => v ? 'Yes' : 'No',
|
||||
onchange: v => {
|
||||
settings.welcomed = v ? undefined : true
|
||||
require('Storage').write('setting.json', settings)
|
||||
},
|
||||
format: v => v ? 'OK' : 'No',
|
||||
onchange: v => require('Storage').write('welcome.settings.json', {welcomed: !v}),
|
||||
},
|
||||
'Run Now': () => load('welcome.app.js'),
|
||||
'< Back': back,
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
0.02: Now refresh battery monitor every minute if LCD on
|
||||
0.03: Tweaks for 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();
|
||||
// refresh once a minute if LCD on
|
||||
if (!batteryInterval)
|
||||
batteryInterval = setInterval(draw, 60000);
|
||||
batteryInterval = setInterval(()=>WIDGETS["bat"].draw(), 60000);
|
||||
} else {
|
||||
if (batteryInterval) {
|
||||
clearInterval(batteryInterval);
|
||||
|
|
|
@ -5,3 +5,4 @@
|
|||
0.06: Show battery percentage as text
|
||||
0.07: Add settings: percentage/color/charger icon
|
||||
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();
|
||||
// refresh once a minute if LCD on
|
||||
if (!batteryInterval)
|
||||
batteryInterval = setInterval(draw, 60000);
|
||||
batteryInterval = setInterval(()=>WIDGETS["batpc"].draw(), 60000);
|
||||
} else {
|
||||
if (batteryInterval) {
|
||||
clearInterval(batteryInterval);
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.02: Tweaks for 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();
|
||||
g.flip();// turns screen on
|
||||
}
|
||||
NRF.on('connected',changed);
|
||||
NRF.on('disconnected',changed);
|
||||
NRF.on('connect',changed);
|
||||
NRF.on('disconnect',changed);
|
||||
WIDGETS["bluetooth"]={area:"tr",width:24,draw:draw};
|
||||
})()
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.02: Now refresh battery monitor every minute if LCD on
|
||||
0.03: Ensure redrawing works with variable size widget system
|
||||
0.04: Fix regression stopping correct widget updates
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
}
|
||||
}
|
||||
function startTimers(){
|
||||
intervalRef = setInterval(draw, 60*1000);
|
||||
intervalRef = setInterval(()=>WIDGETS["wdclk"].draw(), 60*1000);
|
||||
WIDGETS["wdclk"].draw();
|
||||
}
|
||||
Bangle.on('lcdPower', (on) => {
|
||||
|
@ -23,5 +23,5 @@
|
|||
});
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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`);
|
||||
//console.log(`Checking ${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`);
|
||||
}
|
||||
}
|
||||
for (const key in file) {
|
||||
if (!STORAGE_KEYS.includes(key)) ERROR(`App ${app.id}'s ${file.name} has unknown key ${key}`);
|
||||
}
|
||||
});
|
||||
//console.log(fileNames);
|
||||
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 (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;
|
||||
// Chould check CRC here if needed instead of returning 'OK'...
|
||||
// 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") {
|
||||
Progress.hide({sticky:true});
|
||||
return reject("Unexpected response "+(result||""));
|
||||
|
@ -75,12 +75,21 @@ getInstalledApps : () => {
|
|||
Progress.hide({sticky:true});
|
||||
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});
|
||||
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 || "");
|
||||
console.log("getInstalledApps", appList);
|
||||
resolve(appList);
|
||||
});
|
||||
}, true /* callback on newline */);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -56,7 +56,7 @@ function getVersionInfo(appListing, appInstalled) {
|
|||
if (appListing.version)
|
||||
versionText = clicky("v"+appListing.version);
|
||||
} 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) versionText += ", latest "+clicky("v"+appListing.version);
|
||||
canUpdate = true;
|
||||
|
|