update submodules
|
|
@ -245,4 +245,5 @@ module.exports = {
|
|||
})),
|
||||
],
|
||||
ignorePatterns: findGeneratedJS(["apps/", "modules/"]),
|
||||
reportUnusedDisableDirectives: true,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -405,7 +405,7 @@ in an iframe.
|
|||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<script src="../../lib/interface.js"></script>
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
<div id="t">Loading...</div>
|
||||
<script>
|
||||
function onInit() {
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 983 B |
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1 @@
|
|||
atob("MDCBAAAAAAAAAAAAAAAAAAAAH/AAAAAAf/4AAAAB4AeAAAAHgAHgAAAOAABwAAAcAAA4AAA4AMAcAABwAMAOAABgA/AGAADAAeADAAHAAMADgAGAAAABgAGAAAABgAMAAAAAwAMBAAAAwAMDAAAAwAMHwAAAwAMHwAAAwAMDAACAwAMBAADAwAMAAAPgwAMAAAPgwAMAAADAwAGAAACBgAGAAAABgAHAAAADgADAAAADAADgAAAHAAB////+AAB////+AABgAAAGAABgAAAGAABgAAAGAADAAAADAADAAAADAADAAAADAAGAAAABgAGAAAABgAH/////gAP/////wAYAAAAAYAYAAAAAYAf/////4AP/////wAAAAAAAAAAAAAAAAA==")
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
var keyboard = "textinput";
|
||||
var Name = "";
|
||||
Bangle.setLCDTimeout(0);
|
||||
var menuOpen = 1;
|
||||
var answers = new Array("no", "yes","WHAT????","What do you think", "That was a bad question", "YES!!!", "NOOOOO!!", "nope","100%","yup","why should I answer that?","think for yourself","ask again later, I'm busy", "what Was that horrible question","how dare you?","you wanted to hear yes? okay, yes", "Don't get angry when I say no","you are 100% wrong","totally, for sure","hmmm... I'll ponder it and get back to you later","wow, you really have a lot of questions", "NOPE","is the sky blue, hmmm...","I don't have time to answer","How many more questions before you change my name?","theres this thing called wikipedia","hmm... I don't seem to be able to reach the internet right now","if you phrase it like that, yes","Huh, never thought so hard in my life","The winds of time say no");
|
||||
var consonants = new Array("b","c","d","f","g","h","j","k","l","m","n","p","q","r","s","t","v","w","x","y","z");
|
||||
var vowels = new Array("a","e","i","o","u");
|
||||
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
|
||||
function generateName()
|
||||
{
|
||||
Name = "";
|
||||
var nameLength = Math.round(Math.random()*5);
|
||||
for(var i = 0; i < nameLength; i++){
|
||||
var cosonant = consonants[Math.round(Math.random()*consonants.length/2)];
|
||||
var vowel = vowels[Math.round(Math.random()*vowels.length/2)];
|
||||
Name = Name + cosonant + vowel;
|
||||
if(Name == "")
|
||||
{
|
||||
generateName();
|
||||
}
|
||||
}
|
||||
}
|
||||
generateName();
|
||||
function menu()
|
||||
{
|
||||
g.clear();
|
||||
E.showMenu();
|
||||
menuOpen = 1;
|
||||
E.showMenu({
|
||||
"" : { title : Name },
|
||||
"< Back" : () => menu(),
|
||||
"Start" : () => {
|
||||
E.showMenu();
|
||||
g.clear();
|
||||
menuOpen = 0;
|
||||
Drawtext("ask " + Name + " a yes or no question");
|
||||
},
|
||||
"regenerate name" : () => {
|
||||
menu();
|
||||
generateName();
|
||||
},
|
||||
"show answers" : () => {
|
||||
var menu = new Array([]);
|
||||
for(var i = 0; i < answers.length; i++){
|
||||
menu.push({title : answers[i]});
|
||||
}
|
||||
E.showMenu(menu);
|
||||
|
||||
|
||||
},
|
||||
|
||||
"Add answer" : () => {
|
||||
E.showMenu();
|
||||
keyboard.input({}).then(result => {if(result != ""){answers.push(result);} menu();});
|
||||
},
|
||||
"Edit name" : () => {
|
||||
E.showMenu();
|
||||
keyboard.input({}).then(result => {if(result != ""){Name = result;} menu();});
|
||||
|
||||
},
|
||||
"Exit" : () => load(),
|
||||
});
|
||||
}
|
||||
menu();
|
||||
|
||||
var answer;
|
||||
function Drawtext(text)
|
||||
{
|
||||
g.clear();
|
||||
g.setFont("Vector", 20);
|
||||
g.drawString(g.wrapString(text, g.getWidth(), -20).join("\n"));
|
||||
}
|
||||
function WriteAnswer()
|
||||
{
|
||||
if (menuOpen == 0)
|
||||
{
|
||||
var randomnumber = Math.round(Math.random()*answers.length);
|
||||
answer = answers[randomnumber];
|
||||
Drawtext(answer);
|
||||
setTimeout(function() {
|
||||
Drawtext("ask " + Name + " a yes or no question");
|
||||
}, 3000);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
setWatch(function() {
|
||||
menu();
|
||||
|
||||
}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true, edge:"falling"});
|
||||
|
||||
Bangle.on('touch', function(button, xy) { WriteAnswer(); });
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
{ "id": "8ball",
|
||||
"name": "Magic 8 ball",
|
||||
"shortName":"8ball",
|
||||
"icon": "8ball.png",
|
||||
"version":"0.01",
|
||||
"screenshots": [
|
||||
{"url":"screenshot.png"},
|
||||
{"url":"screenshot-1.png"},
|
||||
{"url":"screenshot-2.png"}
|
||||
],
|
||||
"allow_emulator": true,
|
||||
"description": "A very sarcastic magic 8ball",
|
||||
"tags": "game",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"8ball.app.js","url":"app.js"},
|
||||
{"name":"8ball.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
|
@ -5,7 +5,7 @@
|
|||
"description": "A detailed description of my great app",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"7chname.app.js","url":"app.js"},
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
Trigger on 1.04g now, and record 10 samples before trigger
|
||||
0.03: Bangle.js 2 compatibility
|
||||
0.04: Minor code improvements
|
||||
0.05: Can record 100hz, z-axis color changed to yellow, autosave to file, no need select, delete old records
|
||||
|
|
|
|||
|
|
@ -1,179 +1,253 @@
|
|||
//var acc;
|
||||
var HZ = 100;
|
||||
var SAMPLES = 5*HZ; // 5 seconds
|
||||
var SCALE = 5000;
|
||||
var THRESH = 1.04;
|
||||
var SAMPLES = 6 * HZ; // 6 seconds
|
||||
var SCALE = 2000;
|
||||
var THRESH = 1.4;
|
||||
var accelx = new Int16Array(SAMPLES);
|
||||
var accely = new Int16Array(SAMPLES); // North
|
||||
var accelz = new Int16Array(SAMPLES); // Into clock face
|
||||
var timestep = new Int16Array(SAMPLES); // Into clock face
|
||||
var accelIdx = 0;
|
||||
var lastAccel;
|
||||
function accelHandlerTrigger(a) {"ram"
|
||||
if (a.mag*2>THRESH) { // *2 because 8g mode
|
||||
tStart = getTime();
|
||||
g.drawString("Recording",g.getWidth()/2,g.getHeight()/2,1);
|
||||
Bangle.removeListener('accel',accelHandlerTrigger);
|
||||
Bangle.on('accel',accelHandlerRecord);
|
||||
lastAccel.forEach(accelHandlerRecord);
|
||||
accelHandlerRecord(a);
|
||||
} else {
|
||||
if (lastAccel.length>10) lastAccel.shift();
|
||||
lastAccel.push(a);
|
||||
}
|
||||
}
|
||||
function accelHandlerRecord(a) {"ram"
|
||||
var i = accelIdx++;
|
||||
accelx[i] = a.x*SCALE*2;
|
||||
accely[i] = -a.y*SCALE*2;
|
||||
accelz[i] = a.z*SCALE*2;
|
||||
if (accelIdx>=SAMPLES) recordStop();
|
||||
}
|
||||
function recordStart() {"ram"
|
||||
Bangle.setLCDTimeout(0); // force LCD on
|
||||
accelIdx = 0;
|
||||
lastAccel = [];
|
||||
Bangle.accelWr(0x18,0b01110100); // off, +-8g
|
||||
Bangle.accelWr(0x1B,0x03 | 0x40); // 100hz output, ODR/2 filter
|
||||
Bangle.accelWr(0x18,0b11110100); // +-8g
|
||||
Bangle.setPollInterval(10); // 100hz input
|
||||
setTimeout(function() {
|
||||
Bangle.on('accel',accelHandlerTrigger);
|
||||
g.clear(1).setFont("6x8",2).setFontAlign(0,0);
|
||||
g.drawString("Waiting",g.getWidth()/2,g.getHeight()/2);
|
||||
}, 200);
|
||||
}
|
||||
var timestep_start = 0;
|
||||
|
||||
|
||||
function recordStop() {"ram"
|
||||
//console.log("Length:",getTime()-tStart);
|
||||
Bangle.setPollInterval(80); // default poll interval
|
||||
Bangle.accelWr(0x18,0b01101100); // off, +-4g
|
||||
Bangle.accelWr(0x1B,0x0); // default 12.5hz output
|
||||
Bangle.accelWr(0x18,0b11101100); // +-4g
|
||||
Bangle.removeListener('accel',accelHandlerRecord);
|
||||
E.showMessage("Finished");
|
||||
showData();
|
||||
}
|
||||
|
||||
|
||||
function showData() {
|
||||
g.clear(1);
|
||||
var w = g.getWidth()-20; // width
|
||||
var m = g.getHeight()/2; // middle
|
||||
var s = 12; // how many pixels per G
|
||||
g.fillRect(9,0,9,g.getHeight());
|
||||
g.setFontAlign(0,0);
|
||||
for (var l=-8;l<=8;l++)
|
||||
g.drawString(l, 5, m - l*s);
|
||||
|
||||
function plot(a) {
|
||||
g.moveTo(10,m - a[0]*s/SCALE);
|
||||
for (var i=0;i<SAMPLES;i++)
|
||||
g.lineTo(10+i*w/SAMPLES, m - a[i]*s/SCALE);
|
||||
}
|
||||
g.setColor("#0000ff");
|
||||
plot(accelz);
|
||||
g.setColor("#ff0000");
|
||||
plot(accelx);
|
||||
g.setColor("#00ff00");
|
||||
plot(accely);
|
||||
|
||||
// work out stats
|
||||
var maxAccel = 0;
|
||||
var tStart = SAMPLES, tEnd = 0;
|
||||
var vel = 0, maxVel = 0;
|
||||
for (var i=0;i<SAMPLES;i++) {
|
||||
var a = accely[i]/SCALE;
|
||||
if (a>0.1) {
|
||||
if (i<tStart) tStart=i;
|
||||
if (i>tEnd) tEnd=i;
|
||||
function accelHandlerTrigger(a) {
|
||||
"ram"
|
||||
if (a.mag * 2 > THRESH) { // *2 because 8g mode
|
||||
timestep_start = getTime();
|
||||
g.drawString("Recording", g.getWidth() / 2, g.getHeight() / 2, 1);
|
||||
Bangle.removeListener('accel', accelHandlerTrigger);
|
||||
Bangle.on('accel', accelHandlerRecord);
|
||||
lastAccel.forEach(accelHandlerRecord);
|
||||
accelHandlerRecord(a);
|
||||
} else {
|
||||
if (lastAccel.length > 10) lastAccel.shift();
|
||||
lastAccel.push(a);
|
||||
}
|
||||
if (a>maxAccel) maxAccel=a;
|
||||
vel += a/HZ;
|
||||
if (vel>maxVel) maxVel=vel;
|
||||
}
|
||||
g.reset();
|
||||
g.setFont("6x8").setFontAlign(1,0);
|
||||
g.drawString("Max Y Accel: "+maxAccel.toFixed(2)+" g",g.getWidth()-14,g.getHeight()-50);
|
||||
g.drawString("Max Y Vel: "+maxVel.toFixed(2)+" m/s",g.getWidth()-14,g.getHeight()-40);
|
||||
g.drawString("Time moving: "+(tEnd-tStart)/HZ+" s",g.getWidth()-14,g.getHeight()-30);
|
||||
//console.log("End Velocity "+vel);
|
||||
g.setFont("6x8").setFontAlign(0,0,1);
|
||||
g.drawString("FINISH",g.getWidth()-4,g.getHeight()/2);
|
||||
setWatch(function() {
|
||||
showMenu();
|
||||
}, global.BTN2?BTN2:BTN);
|
||||
}
|
||||
|
||||
function accelHandlerRecord(a) {
|
||||
"ram"
|
||||
var i = accelIdx++;
|
||||
accelx[i] = a.x * SCALE * 2; // *2 because of 8g mode
|
||||
accely[i] = -a.y * SCALE * 2;
|
||||
accelz[i] = a.z * SCALE * 2;
|
||||
timestep[i] = (getTime() - timestep_start) * 1000;
|
||||
if (accelIdx >= SAMPLES) recordStop();
|
||||
}
|
||||
|
||||
function recordStart() {
|
||||
"ram"
|
||||
Bangle.setLCDTimeout(0); // force LCD on
|
||||
accelIdx = 0;
|
||||
lastAccel = [];
|
||||
Bangle.accelWr(0x18, 0b01110100); // off, +-8g
|
||||
Bangle.accelWr(0x1B, 0x03 | 0x40); // 100hz output, ODR/2 filter
|
||||
Bangle.accelWr(0x18, 0b11110100); // +-8g
|
||||
Bangle.setPollInterval(10); // 100hz input
|
||||
setTimeout(function() {
|
||||
Bangle.on('accel', accelHandlerTrigger);
|
||||
g.clear(1).setFont("6x8", 2).setFontAlign(0, 0);
|
||||
g.drawString("Waiting", g.getWidth() / 2, g.getHeight() / 2);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
|
||||
function recordStop() {
|
||||
"ram"
|
||||
//console.log("Length:",getTime()-tStart);
|
||||
Bangle.setPollInterval(80); // default poll interval
|
||||
Bangle.accelWr(0x18, 0b01101100); // off, +-4g
|
||||
Bangle.accelWr(0x1B, 0x0); // default 12.5hz output
|
||||
Bangle.accelWr(0x18, 0b11101100); // +-4g
|
||||
Bangle.removeListener('accel', accelHandlerRecord);
|
||||
E.showMessage("Finished");
|
||||
showData(true);
|
||||
}
|
||||
|
||||
|
||||
function showData(save_file) {
|
||||
g.clear(1);
|
||||
let csv_files_N = require("Storage").list(/^acc.*\.csv$/).length;
|
||||
let w_full = g.getWidth();
|
||||
let h = g.getHeight();
|
||||
var w = g.getWidth() - 20; // width
|
||||
var m = g.getHeight() / 2; // middle
|
||||
var s = 12; // how many pixels per G
|
||||
g.fillRect(9, 0, 9, g.getHeight());
|
||||
g.setFontAlign(0, 0);
|
||||
for (var l = -8; l <= 8; l++)
|
||||
g.drawString(l, 5, m - l * s);
|
||||
|
||||
function plot(a) {
|
||||
g.moveTo(10, m - a[0] * s / SCALE);
|
||||
for (var i = 0; i < SAMPLES; i++)
|
||||
g.lineTo(10 + i * w / SAMPLES, m - a[i] * s / SCALE);
|
||||
}
|
||||
g.setColor("#FFFA5F");
|
||||
plot(accelz);
|
||||
g.setColor("#ff0000");
|
||||
plot(accelx);
|
||||
g.setColor("#00ff00");
|
||||
plot(accely);
|
||||
|
||||
// work out stats
|
||||
var maxAccel = 0;
|
||||
var tStart = SAMPLES,
|
||||
tEnd = 0;
|
||||
var max_YZ = 0;
|
||||
for (var i = 0; i < SAMPLES; i++) {
|
||||
var a = Math.abs(accely[i] / SCALE);
|
||||
let a_yz = Math.sqrt(Math.pow(accely[i] / SCALE, 2) + Math.pow(accelz[i] / SCALE, 2));
|
||||
if (a > 0.1) {
|
||||
if (i < tStart) tStart = i;
|
||||
if (i > tEnd) tEnd = i;
|
||||
}
|
||||
if (a > maxAccel) maxAccel = a;
|
||||
if (a_yz > max_YZ) max_YZ = a_yz;
|
||||
}
|
||||
g.reset();
|
||||
g.setFont("6x8").setFontAlign(1, 0);
|
||||
g.drawString("Max X Accel: " + maxAccel.toFixed(2) + " g", g.getWidth() - 14, g.getHeight() - 50);
|
||||
g.drawString("Max YZ Accel: " + max_YZ.toFixed(2) + " g", g.getWidth() - 14, g.getHeight() - 40);
|
||||
g.drawString("Time moving: " + (tEnd - tStart) / HZ + " s", g.getWidth() - 14, g.getHeight() - 30);
|
||||
g.setFont("6x8", 2).setFontAlign(0, 0);
|
||||
g.drawString("File num: " + (csv_files_N + 1), w_full / 2, h - 20);
|
||||
g.setFont("6x8").setFontAlign(0, 0, 1);
|
||||
g.drawString("FINISH", g.getWidth() - 4, g.getHeight() / 2);
|
||||
setWatch(function() {
|
||||
if (save_file) showSaveMenu(); // when select only plot, don't ask for save option
|
||||
else showMenu();
|
||||
}, global.BTN2 ? BTN2 : BTN);
|
||||
}
|
||||
|
||||
function showBig(txt) {
|
||||
g.clear(1);
|
||||
g.setFontVector(80).setFontAlign(0,0);
|
||||
g.drawString(txt,g.getWidth()/2, g.getHeight()/2);
|
||||
g.flip();
|
||||
g.clear(1);
|
||||
g.setFontVector(80).setFontAlign(0, 0);
|
||||
g.drawString(txt, g.getWidth() / 2, g.getHeight() / 2);
|
||||
g.flip();
|
||||
}
|
||||
|
||||
function countDown() {
|
||||
showBig(3);
|
||||
setTimeout(function() {
|
||||
showBig(2);
|
||||
showBig(3);
|
||||
setTimeout(function() {
|
||||
showBig(1);
|
||||
setTimeout(function() {
|
||||
recordStart();
|
||||
}, 800);
|
||||
showBig(2);
|
||||
setTimeout(function() {
|
||||
showBig(1);
|
||||
setTimeout(function() {
|
||||
recordStart();
|
||||
}, 800);
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function showMenu() {
|
||||
Bangle.setLCDTimeout(10); // set timeout for LCD in menu
|
||||
var menu = {
|
||||
"" : { title : "Acceleration Rec" },
|
||||
"Start" : function() {
|
||||
E.showMenu();
|
||||
if (accelIdx==0) countDown();
|
||||
else E.showPrompt("Overwrite Recording?").then(ok=>{
|
||||
if (ok) countDown(); else showMenu();
|
||||
});
|
||||
},
|
||||
"Plot" : function() {
|
||||
E.showMenu();
|
||||
if (accelIdx) showData();
|
||||
else E.showAlert("No Data").then(()=>{
|
||||
showMenu();
|
||||
});
|
||||
},
|
||||
"Save" : function() {
|
||||
E.showMenu();
|
||||
if (accelIdx) showSaveMenu();
|
||||
else E.showAlert("No Data").then(()=>{
|
||||
showMenu();
|
||||
});
|
||||
},
|
||||
"Exit" : function() {
|
||||
load();
|
||||
},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
Bangle.setLCDTimeout(10); // set timeout for LCD in menu
|
||||
var menu = {
|
||||
"": { title: "Acceleration Rec" },
|
||||
"Start": function() {
|
||||
E.showMenu();
|
||||
if (accelIdx == 0) countDown();
|
||||
else E.showPrompt("Overwrite Recording?").then(ok => {
|
||||
if (ok) countDown();
|
||||
else showMenu();
|
||||
});
|
||||
},
|
||||
"Plot": function() {
|
||||
E.showMenu();
|
||||
if (accelIdx) showData(false);
|
||||
else E.showAlert("No Data").then(() => {
|
||||
showMenu();
|
||||
});
|
||||
},
|
||||
"Storage": function() {
|
||||
E.showMenu();
|
||||
if (require("Storage").list(/^acc.*\.csv$/).length)
|
||||
StorageMenu();
|
||||
else
|
||||
E.showAlert("No Data").then(() => {
|
||||
showMenu();
|
||||
});
|
||||
},
|
||||
"Exit": function() {
|
||||
load();
|
||||
},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function showSaveMenu() {
|
||||
var menu = {
|
||||
"" : { title : "Save" }
|
||||
};
|
||||
[1,2,3,4,5,6].forEach(i=>{
|
||||
var fn = "accelrec."+i+".csv";
|
||||
var exists = require("Storage").read(fn)!==undefined;
|
||||
menu["Recording "+i+(exists?" *":"")] = function() {
|
||||
var csv = "";
|
||||
for (var i=0;i<SAMPLES;i++)
|
||||
csv += `${accelx[i]/SCALE},${accely[i]/SCALE},${accelz[i]/SCALE}\n`;
|
||||
require("Storage").write(fn,csv);
|
||||
showMenu();
|
||||
};
|
||||
});
|
||||
menu["< Back"] = function() {showMenu();};
|
||||
E.showMenu(menu);
|
||||
E.showPrompt("Save recording?").then(ok => {
|
||||
if (ok)
|
||||
SaveFile();
|
||||
else
|
||||
showMenu();
|
||||
});
|
||||
}
|
||||
|
||||
showMenu();
|
||||
function SaveFile() {
|
||||
let csv_files_N = require("Storage").list(/^acc.*\.csv$/).length;
|
||||
//if (csv_files_N > 20)
|
||||
// E.showMessage("Storage is full");
|
||||
// showMenu();
|
||||
let csv = "";
|
||||
let date = new Date();
|
||||
let fn = "accelrec_" + date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds() + "_" + (csv_files_N + 1) + ".csv";
|
||||
E.showMessage("Saveing to file \n" + fn);
|
||||
for (var i = 0; i < SAMPLES; i++)
|
||||
csv += `${timestep[i]},${accelx[i]/SCALE},${accely[i]/SCALE},${accelz[i]/SCALE}\n`;
|
||||
require("Storage").write(fn, csv);
|
||||
showMenu();
|
||||
}
|
||||
|
||||
//Show saved csv files
|
||||
function StorageMenu() {
|
||||
var menu = {
|
||||
"": {
|
||||
title: "Storage"
|
||||
}
|
||||
};
|
||||
let csv_files = require("Storage").list(/^acc.*\.csv$/);
|
||||
var inx = 0;
|
||||
csv_files.forEach(fn => {
|
||||
inx++;
|
||||
menu[inx + ". " + fn] = function() {
|
||||
StorageOptions(fn);
|
||||
};
|
||||
});
|
||||
menu["< Back"] = function() {
|
||||
showMenu();
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function StorageOptions(file) {
|
||||
let menu = {
|
||||
"": {
|
||||
title: "Options"
|
||||
},
|
||||
"Plot": function() {
|
||||
showMenu();
|
||||
},
|
||||
"Delete": function() {
|
||||
E.showMenu();
|
||||
E.showPrompt("Delete recording?").then(ok => {
|
||||
if (ok)
|
||||
DeleteRecord(file);
|
||||
else
|
||||
StorageMenu();
|
||||
});
|
||||
},
|
||||
"< Back": function() {
|
||||
StorageMenu();
|
||||
},
|
||||
};
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function DeleteRecord(file) {
|
||||
E.showMessage("Deleteing file \n" + file);
|
||||
require("Storage").erase(file);
|
||||
StorageMenu();
|
||||
}
|
||||
showMenu();
|
||||
|
|
@ -37,7 +37,7 @@ function getData() {
|
|||
</div>`;
|
||||
promise = promise.then(function() {
|
||||
document.querySelector(`.btn[fn='${fn}'][act='save']`).addEventListener("click", function() {
|
||||
Util.saveCSV(fn.slice(0,-4), "X,Y,Z\n"+fileData[fn]);
|
||||
Util.saveCSV(fn.slice(0,-4), "Time,X,Y,Z\n"+fileData[fn]);
|
||||
});
|
||||
document.querySelector(`.btn[fn='${fn}'][act='delete']`).addEventListener("click", function() {
|
||||
Util.showModal("Deleting...");
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "accelrec",
|
||||
"name": "Acceleration Recorder",
|
||||
"shortName": "Accel Rec",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "This app puts the Bangle's accelerometer into 100Hz mode and reads 2 seconds worth of data after movement starts. The data can then be exported back to the PC.",
|
||||
"icon": "app.png",
|
||||
"tags": "",
|
||||
|
|
|
|||
|
|
@ -50,3 +50,4 @@
|
|||
0.45: Fix new alarm when selectedAlarm is undefined
|
||||
0.46: Show alarm groups if the Show Group setting is ON. Scroll alarms menu back to previous position when getting back to it.
|
||||
0.47: Fix wrap around when snoozed through midnight
|
||||
0.48: Use datetimeinput for Events, if available. Scroll back when getting out of group. Menu date format setting for shorter dates on current year.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
This app allows you to add/modify any alarms, timers and events.
|
||||
|
||||
Optional: When a keyboard app is detected, you can add a message to display when any of these is triggered.
|
||||
Optional: When a keyboard app is detected, you can add a message to display when any of these is triggered. If a datetime input app (e.g. datetime_picker) is detected, it will be used for the selection of the date+time of events.
|
||||
|
||||
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.
|
||||
|
||||
|
|
|
|||
|
|
@ -50,13 +50,17 @@ function handleFirstDayOfWeek(dow) {
|
|||
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
|
||||
|
||||
function getLabel(e) {
|
||||
const dateStr = e.date && require("locale").date(new Date(e.date), 1);
|
||||
const dateStr = getDateText(e.date);
|
||||
return (e.timer
|
||||
? require("time_utils").formatDuration(e.timer)
|
||||
: (dateStr ? `${dateStr}${e.rp?"*":""} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeRepeat(e)}` : ""))
|
||||
) + (e.msg ? ` ${e.msg}` : "");
|
||||
}
|
||||
|
||||
function getDateText(d) {
|
||||
return d && (settings.menuDateFormat === "mmdd" ? d.substring(d.startsWith(new Date().getFullYear()) ? 5 : 0) : require("locale").date(new Date(d), 1));
|
||||
}
|
||||
|
||||
function trimLabel(label, maxLength) {
|
||||
if(settings.showOverflow) return label;
|
||||
return (label.length > maxLength
|
||||
|
|
@ -75,10 +79,10 @@ function formatAlarmProperty(msg) {
|
|||
}
|
||||
}
|
||||
|
||||
function showMainMenu(scroll, group) {
|
||||
function showMainMenu(scroll, group, scrollback) {
|
||||
const menu = {
|
||||
"": { "title": group || /*LANG*/"Alarms & Timers", scroll: scroll },
|
||||
"< Back": () => group ? showMainMenu() : load(),
|
||||
"< Back": () => group ? showMainMenu(scrollback) : load(),
|
||||
/*LANG*/"New...": () => showNewMenu(group)
|
||||
};
|
||||
const getGroups = settings.showGroup && !group;
|
||||
|
|
@ -98,7 +102,7 @@ function showMainMenu(scroll, group) {
|
|||
});
|
||||
|
||||
if (!group) {
|
||||
Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g));
|
||||
Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g, scroller.scroll));
|
||||
menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
|
||||
}
|
||||
|
||||
|
|
@ -138,6 +142,8 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
|
|||
var title = date ? (isNew ? /*LANG*/"New Event" : /*LANG*/"Edit Event") : (isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm");
|
||||
var keyboard = "textinput";
|
||||
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
|
||||
var datetimeinput;
|
||||
try {datetimeinput = require("datetimeinput");} catch(e) {datetimeinput = null;}
|
||||
|
||||
const menu = {
|
||||
"": { "title": title },
|
||||
|
|
@ -145,41 +151,66 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
|
|||
prepareAlarmForSave(alarm, alarmIndex, time, date);
|
||||
saveAndReload();
|
||||
showMainMenu(scroll, group);
|
||||
},
|
||||
/*LANG*/"Hour": {
|
||||
value: time.h,
|
||||
format: v => ("0" + v).substr(-2),
|
||||
min: 0,
|
||||
max: 23,
|
||||
wrap: true,
|
||||
onchange: v => time.h = v
|
||||
},
|
||||
/*LANG*/"Minute": {
|
||||
value: time.m,
|
||||
format: v => ("0" + v).substr(-2),
|
||||
min: 0,
|
||||
max: 59,
|
||||
wrap: true,
|
||||
onchange: v => time.m = v
|
||||
},
|
||||
/*LANG*/"Day": {
|
||||
value: date ? date.getDate() : null,
|
||||
min: 1,
|
||||
max: 31,
|
||||
wrap: true,
|
||||
onchange: v => date.setDate(v)
|
||||
},
|
||||
/*LANG*/"Month": {
|
||||
value: date ? date.getMonth() + 1 : null,
|
||||
format: v => require("date_utils").month(v),
|
||||
onchange: v => date.setMonth((v+11)%12)
|
||||
},
|
||||
/*LANG*/"Year": {
|
||||
value: date ? date.getFullYear() : null,
|
||||
min: new Date().getFullYear(),
|
||||
max: 2100,
|
||||
onchange: v => date.setFullYear(v)
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if (alarm.date && datetimeinput) {
|
||||
menu[`${getDateText(date.toLocalISOString().slice(0,10))} ${require("time_utils").formatTime(time)}`] = {
|
||||
value: date,
|
||||
format: v => "",
|
||||
onchange: v => {
|
||||
setTimeout(() => {
|
||||
var datetime = new Date(v.getTime());
|
||||
datetime.setHours(time.h, time.m);
|
||||
datetimeinput.input({datetime}).then(result => {
|
||||
time.h = result.getHours();
|
||||
time.m = result.getMinutes();
|
||||
prepareAlarmForSave(alarm, alarmIndex, time, result, true);
|
||||
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate, scroll, group);
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
};
|
||||
} else {
|
||||
Object.assign(menu, {
|
||||
/*LANG*/"Hour": {
|
||||
value: time.h,
|
||||
format: v => ("0" + v).substr(-2),
|
||||
min: 0,
|
||||
max: 23,
|
||||
wrap: true,
|
||||
onchange: v => time.h = v
|
||||
},
|
||||
/*LANG*/"Minute": {
|
||||
value: time.m,
|
||||
format: v => ("0" + v).substr(-2),
|
||||
min: 0,
|
||||
max: 59,
|
||||
wrap: true,
|
||||
onchange: v => time.m = v
|
||||
},
|
||||
/*LANG*/"Day": {
|
||||
value: date ? date.getDate() : null,
|
||||
min: 1,
|
||||
max: 31,
|
||||
wrap: true,
|
||||
onchange: v => date.setDate(v)
|
||||
},
|
||||
/*LANG*/"Month": {
|
||||
value: date ? date.getMonth() + 1 : null,
|
||||
format: v => require("date_utils").month(v),
|
||||
onchange: v => date.setMonth((v+11)%12)
|
||||
},
|
||||
/*LANG*/"Year": {
|
||||
value: date ? date.getFullYear() : null,
|
||||
min: new Date().getFullYear(),
|
||||
max: 2100,
|
||||
onchange: v => date.setFullYear(v)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Object.assign(menu, {
|
||||
/*LANG*/"Message": {
|
||||
value: alarm.msg,
|
||||
format: formatAlarmProperty,
|
||||
|
|
@ -241,7 +272,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
|
|||
saveAndReload();
|
||||
showMainMenu(scroll, group);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
if (!keyboard) delete menu[/*LANG*/"Message"];
|
||||
if (!keyboard || !settings.showGroup) delete menu[/*LANG*/"Group"];
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "alarm",
|
||||
"name": "Alarms & Timers",
|
||||
"shortName": "Alarms",
|
||||
"version": "0.47",
|
||||
"version": "0.48",
|
||||
"description": "Set alarms and timers on your Bangle",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm",
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
E.toArrayBuffer(atob("MDADAAAAHA4HHAAAHA4HA4HA4HAAAAA4//////AAH//////6///AAAA///6//9AAV/////////XAAAA//////6AAv//X//////4AAAA//////4AA////X/////4AAAA//////4AA/////6////4AAAA//////4AA///6vX///v4AAAAAA////4AA///4A//6//AAAAAAF///64AAHA4CFX////AAAAAAH//X/oAAAAAAH6///4AAAAAAH////AAAAAAAH////QAAAAAAH////AAAAAAA/////AAAAAAAH////AAAAAAC////4AAAAAAAH////AAAAAAH////4AAAAAAA6v///AAAAAA//X/9QAAAAAAA////VAAAAAA////4AAAAAAAA////4AAAAAH////4AAAAAAAA////4AAAAA/6///AAAAAAAAA////4AAAAA////6AAAAAAAAF////4AAAAH/X/94AAAAAAAAH/X//oAAAAX////AAAAAAAAAH////AAAAA/////AAAAAAAAAH////AEQAB+///4AwEAAAAAAH////AigAH////4EUGEAAAAAX////A0GA///3/AmEUigAAAAjGMYxEw0AxxGOIGg0w0igAAGikUwmGmmgwEUwmmGmk0wwAAmmmmmmmGmmgimmmmmmmGmgAGmmmGgGmmmmAmmmmiE0w00wAE0000wE000wwmmmmiA0000wAE00U0AGmmmmA0000wE0U00wAAAAAAAmmmmmAAAAAAGmmmmAAAAAAAE0000wAAAAAE00U00AAAAAACk0000QAAAAAmmmmmgAAAAAAGmmmmigAAAAA00000AAAAAAA00000UAAAAAmmmmmEAAAAAA000w00AAAAAGmmmmmAAAAAAE00000QAAAAE0U000wAAAAAA00000wAAAAA000mmiAAAAAAmmmmmiAAAAAmimmmkQAAAAAGmmmmmAAAAAE00000QAAAAAA00000wmEwmAmmmmmmEwmEwAE0000w00000wmmmmmmimmmgAGmmmmmmmmmGEw00mmmmmmmgAGmmmmmmmmmGE0w00000000AA0000U000mmmA000000000wwAGmmmmmmmmmEU0GmmmmmmmmAAAAiAEACgAECAAgCEAAiAEAAA"))
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
var settings = Object.assign({
|
||||
// default values
|
||||
showWidgets: false,
|
||||
alternativeColor: false,
|
||||
}, require('Storage').readJSON("ashadyclock.json", true) || {});
|
||||
|
||||
let drawTimeout;
|
||||
// schedule a draw for the next minute
|
||||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
let palBottom;
|
||||
if (settings.alternativeColor) {
|
||||
palBottom = new Uint16Array(E.toArrayBuffer(E.toFlatString(new Uint16Array([
|
||||
g.toColor("#000"),
|
||||
g.toColor("#000"),
|
||||
g.toColor("#0FF"),
|
||||
g.toColor("#0FF"),
|
||||
g.toColor("#00F"),
|
||||
g.toColor("#000"),
|
||||
g.toColor("#00F"),
|
||||
g.toColor("#000")
|
||||
]).buffer)));
|
||||
} else {
|
||||
palBottom = new Uint16Array(E.toArrayBuffer(E.toFlatString(new Uint16Array([
|
||||
g.toColor("#000"),
|
||||
g.toColor("#000"),
|
||||
g.toColor("#F00"),
|
||||
g.toColor("#FF0"),
|
||||
g.toColor("#00F"),
|
||||
g.toColor("#000"),
|
||||
g.toColor("#FF0"),
|
||||
g.toColor("#000")
|
||||
]).buffer)));
|
||||
}
|
||||
|
||||
let palTop = new Uint16Array(E.toArrayBuffer(E.toFlatString(new Uint16Array([
|
||||
g.toColor("#FFF"),
|
||||
g.toColor("#000"),
|
||||
g.toColor("#FFF"),
|
||||
g.toColor("#FFF"),
|
||||
g.toColor("#00F"),
|
||||
g.toColor("#000"),
|
||||
g.toColor("#FFF"),
|
||||
g.toColor("#000"),
|
||||
]).buffer)));
|
||||
|
||||
let xOffset = (g.getWidth() - 176) / 2;
|
||||
let yOffset = (g.getHeight() - 176) / 2;
|
||||
|
||||
function drawTop(d0, d1) {
|
||||
if (settings.showWidgets && g.getHeight()<=176) {
|
||||
drawNumber(d1, 82 + xOffset, 24 + yOffset, palTop, {scale: 0.825});
|
||||
drawNumber(d0, 13 + xOffset, 24 + yOffset, palTop, {scale: 0.825});
|
||||
} else {
|
||||
drawNumber(d1, 80, 0, palTop);
|
||||
drawNumber(d0, -1, 0, palTop);
|
||||
}
|
||||
}
|
||||
|
||||
function drawBottom(d0, d1) {
|
||||
if (settings.showWidgets && g.getHeight()<=176) {
|
||||
drawNumber(d1, 82 + xOffset, 92 + yOffset, palBottom, {scale: 0.825});
|
||||
drawNumber(d0, 13 + xOffset, 92 + yOffset, palBottom, {scale: 0.825});
|
||||
} else {
|
||||
drawNumber(d1, 80, 75, palBottom);
|
||||
drawNumber(d0, -1, 75, palBottom);
|
||||
}
|
||||
}
|
||||
|
||||
function drawNumber(number, x, y, palette, options) {
|
||||
let image =
|
||||
{
|
||||
width : 98, height : 100, bpp : 3,
|
||||
transparent: 4,
|
||||
buffer : require("Storage").read("ashadyclock." + number +".bin")
|
||||
};
|
||||
image.palette = palette;
|
||||
g.drawImage(image, x, y, options);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
let d = new Date();
|
||||
g.clearRect(0, settings.showWidgets ? 24 : 0, g.getWidth(),g.getHeight());
|
||||
|
||||
drawBottom(Math.floor(d.getMinutes()/10), d.getMinutes() % 10);
|
||||
drawTop(Math.floor(d.getHours()/10), d.getHours() % 10);
|
||||
|
||||
queueDraw();
|
||||
}
|
||||
|
||||
g.clear();
|
||||
// draw immediately at first
|
||||
draw();
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
Bangle.setUI("clock");
|
||||
|
||||
if(settings.showWidgets) {
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -0,0 +1,27 @@
|
|||
{ "id": "ashadyclock",
|
||||
"name": "A Shady Clock",
|
||||
"shortName":"Shady Clk",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "A nice clock with drop shadow. Hours and minutes. Configure color and widgets in settings. Create any color combination with the existing images by changing only the app color values.",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"screenshots": [{"url":"screenshot-1.png"},{"url":"screenshot.png"}],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"ashadyclock.app.js","url":"app.js"},
|
||||
{"name":"ashadyclock.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"ashadyclock.0.bin","url":"0.bin"},
|
||||
{"name":"ashadyclock.1.bin","url":"1.bin"},
|
||||
{"name":"ashadyclock.2.bin","url":"2.bin"},
|
||||
{"name":"ashadyclock.3.bin","url":"3.bin"},
|
||||
{"name":"ashadyclock.4.bin","url":"4.bin"},
|
||||
{"name":"ashadyclock.5.bin","url":"5.bin"},
|
||||
{"name":"ashadyclock.6.bin","url":"6.bin"},
|
||||
{"name":"ashadyclock.7.bin","url":"7.bin"},
|
||||
{"name":"ashadyclock.8.bin","url":"8.bin"},
|
||||
{"name":"ashadyclock.9.bin","url":"9.bin"},
|
||||
{"name":"ashadyclock.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [{"name":"ashadyclock.json"}]
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 956 B |
|
|
@ -0,0 +1,32 @@
|
|||
(function(back) {
|
||||
var FILE = "ashadyclock.json";
|
||||
// Load settings
|
||||
var settings = Object.assign({
|
||||
showWidgets: false,
|
||||
alternativeColor: false,
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function writeSettings() {
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
// Show the menu
|
||||
E.showMenu({
|
||||
"" : { "title" : "Shady Clck" },
|
||||
"< Back" : () => back(),
|
||||
'Show Widgets': {
|
||||
value: !!settings.showWidgets, // !! converts undefined to false
|
||||
onchange: v => {
|
||||
settings.showWidgets = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
'Blue Color': {
|
||||
value: !!settings.alternativeColor, // !! converts undefined to false
|
||||
onchange: v => {
|
||||
settings.alternativeColor = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
||||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# App Name
|
||||
|
||||
Beep Test
|
||||
|
||||
## Usage
|
||||
|
||||
Mark out a 20m space
|
||||
Click the side button to start the test
|
||||
Shuttle run between your markers when the watch buzzes
|
||||
Push the button when you need to stop
|
||||
|
||||
## Features
|
||||
|
||||
Buzzing on each shuttle run
|
||||
Results page with vO2max and total distance covered.
|
||||
|
||||
## Controls
|
||||
|
||||
Side button starts, stops and resets the app.
|
||||
|
||||
## Requests
|
||||
|
||||
bb0x88 on giuthub
|
||||
|
||||
## Creator
|
||||
|
||||
Blade
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///+f8lky6f8HFmqBRMK1WgBAtUBYUABYtVqtAgEoAIQACioLBqALHBQIABBZMFEgIjHgEBqtUHY4aDKZA+CoBrIBYJJBBZJuCAA3VBYkC1QABGoJhDBYxTBBYUFEQoLDoEVSgIADO4ILCUASdGqtRGIYLFKoY7CIwdUEwJtBBYY6CqADBFwoLDDYIuFIwQUBigLITJQLFHYKNEHAgLGXw6NDBZbKHTIYLLKg6lDBY4KDEY5EIIwahFHQoKIBYIrHIwYLLuALJHRTcHAAjcGAEwA=="))
|
||||
|
||||
|
|
@ -0,0 +1,274 @@
|
|||
var Layout = require("Layout");
|
||||
|
||||
// Beep Test Data
|
||||
const BEET_TEST_DATA = [
|
||||
{ shuttles: 7, timePerShuttle: 9.0, totalTime: 63.0, distancePerLevel: 140 },
|
||||
{ shuttles: 8, timePerShuttle: 8.0, totalTime: 64.0, distancePerLevel: 160 },
|
||||
{ shuttles: 8, timePerShuttle: 7.58, totalTime: 60.6, distancePerLevel: 160 },
|
||||
{ shuttles: 9, timePerShuttle: 7.2, totalTime: 64.8, distancePerLevel: 180 },
|
||||
{ shuttles: 9, timePerShuttle: 6.86, totalTime: 61.7, distancePerLevel: 180 },
|
||||
{
|
||||
shuttles: 10,
|
||||
timePerShuttle: 6.55,
|
||||
totalTime: 65.5,
|
||||
distancePerLevel: 200,
|
||||
},
|
||||
{
|
||||
shuttles: 10,
|
||||
timePerShuttle: 6.26,
|
||||
totalTime: 62.6,
|
||||
distancePerLevel: 200,
|
||||
},
|
||||
{ shuttles: 11, timePerShuttle: 6.0, totalTime: 66.0, distancePerLevel: 220 },
|
||||
{
|
||||
shuttles: 11,
|
||||
timePerShuttle: 5.76,
|
||||
totalTime: 63.4,
|
||||
distancePerLevel: 220,
|
||||
},
|
||||
{
|
||||
shuttles: 11,
|
||||
timePerShuttle: 5.54,
|
||||
totalTime: 60.9,
|
||||
distancePerLevel: 220,
|
||||
},
|
||||
{
|
||||
shuttles: 12,
|
||||
timePerShuttle: 5.33,
|
||||
totalTime: 64.0,
|
||||
distancePerLevel: 240,
|
||||
},
|
||||
{
|
||||
shuttles: 12,
|
||||
timePerShuttle: 5.14,
|
||||
totalTime: 61.7,
|
||||
distancePerLevel: 240,
|
||||
},
|
||||
{
|
||||
shuttles: 13,
|
||||
timePerShuttle: 4.97,
|
||||
totalTime: 64.6,
|
||||
distancePerLevel: 260,
|
||||
},
|
||||
{ shuttles: 13, timePerShuttle: 4.8, totalTime: 62.4, distancePerLevel: 260 },
|
||||
{
|
||||
shuttles: 13,
|
||||
timePerShuttle: 4.65,
|
||||
totalTime: 60.4,
|
||||
distancePerLevel: 260,
|
||||
},
|
||||
{ shuttles: 14, timePerShuttle: 4.5, totalTime: 63.0, distancePerLevel: 280 },
|
||||
{
|
||||
shuttles: 14,
|
||||
timePerShuttle: 4.36,
|
||||
totalTime: 61.1,
|
||||
distancePerLevel: 280,
|
||||
},
|
||||
{
|
||||
shuttles: 15,
|
||||
timePerShuttle: 4.24,
|
||||
totalTime: 63.5,
|
||||
distancePerLevel: 300,
|
||||
},
|
||||
{
|
||||
shuttles: 15,
|
||||
timePerShuttle: 4.11,
|
||||
totalTime: 61.7,
|
||||
distancePerLevel: 300,
|
||||
},
|
||||
{ shuttles: 16, timePerShuttle: 4.0, totalTime: 64.0, distancePerLevel: 320 },
|
||||
{
|
||||
shuttles: 16,
|
||||
timePerShuttle: 3.89,
|
||||
totalTime: 62.3,
|
||||
distancePerLevel: 320,
|
||||
},
|
||||
];
|
||||
|
||||
// VO2max Data
|
||||
const VO2MAX_DATA = [
|
||||
{ level: 1, vo2max: 16.7 },
|
||||
{ level: 2, vo2max: 23.0 },
|
||||
{ level: 3, vo2max: 26.2 },
|
||||
{ level: 4, vo2max: 29.3 },
|
||||
{ level: 5, vo2max: 32.5 },
|
||||
{ level: 6, vo2max: 35.7 },
|
||||
{ level: 7, vo2max: 38.8 },
|
||||
{ level: 8, vo2max: 42.0 },
|
||||
{ level: 9, vo2max: 45.1 },
|
||||
{ level: 10, vo2max: 48.3 },
|
||||
{ level: 11, vo2max: 51.5 },
|
||||
{ level: 12, vo2max: 54.6 },
|
||||
{ level: 13, vo2max: 57.8 },
|
||||
{ level: 14, vo2max: 60.9 },
|
||||
{ level: 15, vo2max: 64.1 },
|
||||
{ level: 16, vo2max: 67.3 },
|
||||
{ level: 17, vo2max: 70.4 },
|
||||
{ level: 18, vo2max: 73.6 },
|
||||
{ level: 19, vo2max: 76.7 },
|
||||
{ level: 20, vo2max: 79.9 },
|
||||
{ level: 21, vo2max: 83.0 },
|
||||
];
|
||||
|
||||
let currentLevel = 0;
|
||||
let currentShuttle = 0;
|
||||
let timeRemaining = 0;
|
||||
let intervalId;
|
||||
let beepTestLayout;
|
||||
let testState = "start"; // 'start' | 'running' | 'result'
|
||||
|
||||
function initBeepTestLayout() {
|
||||
beepTestLayout = new Layout(
|
||||
{
|
||||
type: "v",
|
||||
c: [
|
||||
{ type: "txt", font: "30%", pad: 0, label: "Start Test", id: "status" },
|
||||
{ type: "txt", font: "15%", pad: 0, label: "", id: "level" },
|
||||
{ type: "txt", font: "10%", pad: 0, label: "", id: "vo2max" }, // Smaller font for VO2max
|
||||
{ type: "txt", font: "10%", pad: 0, label: "", id: "distance" }, // Smaller font for Distance
|
||||
],
|
||||
},
|
||||
{
|
||||
btns: [
|
||||
{
|
||||
label: "Start/Stop",
|
||||
cb: (l) => {
|
||||
if (testState === "start") {
|
||||
startTest();
|
||||
} else if (testState === "running") {
|
||||
stopTest();
|
||||
} else {
|
||||
showStartScreen();
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function showStartScreen() {
|
||||
testState = "start";
|
||||
g.clear();
|
||||
beepTestLayout.clear(beepTestLayout.status);
|
||||
beepTestLayout.status.label = "Start\nTest";
|
||||
beepTestLayout.clear(beepTestLayout.level);
|
||||
beepTestLayout.level.label = "";
|
||||
beepTestLayout.clear(beepTestLayout.vo2max); // Clear VO2max text
|
||||
beepTestLayout.vo2max.label = "";
|
||||
beepTestLayout.clear(beepTestLayout.distance); // Clear Distance text
|
||||
beepTestLayout.distance.label = "";
|
||||
beepTestLayout.render();
|
||||
}
|
||||
|
||||
function startTest() {
|
||||
testState = "running";
|
||||
currentLevel = 0;
|
||||
currentShuttle = 0;
|
||||
Bangle.buzz(2000); // Buzz for 2 seconds at the start of the test
|
||||
runLevel();
|
||||
}
|
||||
|
||||
function runLevel() {
|
||||
if (currentLevel >= BEET_TEST_DATA.length) {
|
||||
stopTest();
|
||||
return;
|
||||
}
|
||||
|
||||
const levelData = BEET_TEST_DATA[currentLevel];
|
||||
timeRemaining = levelData.timePerShuttle * 1000; // Convert to milliseconds
|
||||
updateDisplay();
|
||||
|
||||
if (intervalId) clearInterval(intervalId);
|
||||
intervalId = setInterval(() => {
|
||||
if (timeRemaining <= 0) {
|
||||
currentShuttle++;
|
||||
Bangle.buzz(100); // Short buzz after each shuttle
|
||||
|
||||
if (currentShuttle >= levelData.shuttles) {
|
||||
// Buzz longer or twice at the end of each level
|
||||
Bangle.buzz(1000); // Buzz for 1 second at level end
|
||||
setTimeout(() => Bangle.buzz(1000), 500); // Buzz again after 0.5 seconds
|
||||
currentLevel++;
|
||||
currentShuttle = 0;
|
||||
runLevel();
|
||||
return;
|
||||
}
|
||||
|
||||
timeRemaining = levelData.timePerShuttle * 1000; // Reset to original time for the next shuttle
|
||||
}
|
||||
|
||||
updateDisplay();
|
||||
timeRemaining -= 100; // Decrement time by 100 milliseconds
|
||||
}, 100); // Update every 100 milliseconds
|
||||
}
|
||||
|
||||
function updateDisplay() {
|
||||
g.clear(); // Clear the entire screen
|
||||
beepTestLayout.status.label = formatTime(timeRemaining);
|
||||
beepTestLayout.level.label = `Level: ${currentLevel + 1}.${currentShuttle + 1}`;
|
||||
beepTestLayout.render();
|
||||
}
|
||||
|
||||
function stopTest() {
|
||||
g.clear(); // Clear the entire screen
|
||||
testState = "result";
|
||||
clearInterval(intervalId);
|
||||
|
||||
// Determine previous level and shuttle
|
||||
let prevLevel = currentLevel;
|
||||
let prevShuttle = currentShuttle;
|
||||
|
||||
if (prevShuttle === 0) {
|
||||
if (prevLevel > 0) {
|
||||
prevLevel--;
|
||||
prevShuttle = BEET_TEST_DATA[prevLevel].shuttles - 1;
|
||||
} else {
|
||||
prevShuttle = 0;
|
||||
}
|
||||
} else {
|
||||
prevShuttle--;
|
||||
}
|
||||
|
||||
// Determine VO2max and total distance
|
||||
const vo2max = getVO2max(prevLevel + 1);
|
||||
const totalDistance = calculateTotalDistance(prevLevel + 1);
|
||||
|
||||
beepTestLayout.clear(beepTestLayout.status);
|
||||
beepTestLayout.status.label = "Result";
|
||||
beepTestLayout.clear(beepTestLayout.level);
|
||||
beepTestLayout.level.label = `Level: ${prevLevel + 1}.${prevShuttle + 1}`;
|
||||
beepTestLayout.clear(beepTestLayout.vo2max);
|
||||
beepTestLayout.vo2max.label = `VO2max: ${vo2max}`;
|
||||
beepTestLayout.clear(beepTestLayout.distance);
|
||||
beepTestLayout.distance.label = `Distance: ${totalDistance} m`;
|
||||
beepTestLayout.render();
|
||||
}
|
||||
|
||||
function getVO2max(level) {
|
||||
const result = VO2MAX_DATA.find((item) => item.level === level);
|
||||
return result ? result.vo2max : "N/A";
|
||||
}
|
||||
|
||||
function calculateTotalDistance(level) {
|
||||
// Calculate the total number of shuttles completed
|
||||
let totalShuttles = 0;
|
||||
for (let i = 0; i < level - 1; i++) {
|
||||
totalShuttles += BEET_TEST_DATA[i].shuttles;
|
||||
}
|
||||
const levelData = BEET_TEST_DATA[level - 1];
|
||||
totalShuttles += levelData.shuttles; // Add the shuttles completed in the current level
|
||||
const distancePerShuttle = 20; // Distance per shuttle in meters
|
||||
return totalShuttles * distancePerShuttle; // Total distance
|
||||
}
|
||||
|
||||
function formatTime(milliseconds) {
|
||||
let seconds = Math.floor(milliseconds / 1000);
|
||||
let tenths = Math.floor((milliseconds % 1000) / 100); // Get tenths of a second
|
||||
return (seconds < 10 ? "" : "") + seconds + "." + tenths; // Display only the tenths digit
|
||||
}
|
||||
|
||||
// Initialize the app
|
||||
Bangle.setLCDPower(1); // Keep the watch LCD lit up
|
||||
initBeepTestLayout();
|
||||
showStartScreen();
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "beeptest",
|
||||
"name": "Beep Test",
|
||||
"shortName": "Beep Test",
|
||||
"version": "0.01",
|
||||
"description": "Aerobic fitness test created by Léger & Lambert",
|
||||
"icon": "beeptest.png",
|
||||
"tags": "Health",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{ "name": "beeptest.app.js", "url": "beeptest.js" },
|
||||
{ "name": "beeptest.img", "url": "app-icon.js", "evaluate": true }
|
||||
]
|
||||
}
|
||||
|
|
@ -69,4 +69,6 @@
|
|||
0.58: "Make Connectable" temporarily bypasses the whitelist
|
||||
0.59: Whitelist: Try to resolve peer addresses using NRF.resolveAddress() - for 2v19 or 2v18 cutting edge builds
|
||||
0.60: Minor code improvements
|
||||
0.61: Instead of breaking execution with an Exception when updating boot, just use if..else (fix 'Uncaught undefined')
|
||||
0.61: Instead of breaking execution with an Exception when updating boot, just use if..else (fix 'Uncaught undefined')
|
||||
0.62: Handle setting for configuring BLE privacy
|
||||
0.63: Only set BLE `display:1` if we have a passkey
|
||||
|
|
|
|||
|
|
@ -78,7 +78,12 @@ if (global.save) boot += `global.save = function() { throw new Error("You can't
|
|||
// Apply any settings-specific stuff
|
||||
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
|
||||
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
|
||||
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`;
|
||||
if (s.bleprivacy || (s.passkey!==undefined && s.passkey.length==6)) {
|
||||
let passkey = s.passkey ? `passkey:${E.toJS(s.passkey.toString())},display:1,mitm:1,` : "";
|
||||
let privacy = s.bleprivacy ? `privacy:${E.toJS(s.bleprivacy)},` : "";
|
||||
boot+=`NRF.setSecurity({${passkey}${privacy}});\n`;
|
||||
}
|
||||
if (s.blename === false) boot+=`NRF.setAdvertising({},{showName:false});\n`;
|
||||
if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist) { let whitelist = (require('Storage').readJSON('setting.json',1)||{}).whitelist; if (NRF.resolveAddress !== undefined) { let resolvedAddr = NRF.resolveAddress(addr); if (resolvedAddr !== undefined) addr = resolvedAddr + " (resolved)"; } if (!whitelist.includes(addr)) NRF.disconnect(); }});\n`;
|
||||
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
|
||||
// ================================================== FIXING OLDER FIRMWARES
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "boot",
|
||||
"name": "Bootloader",
|
||||
"version": "0.61",
|
||||
"version": "0.63",
|
||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||
"icon": "bootloader.png",
|
||||
"type": "bootloader",
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: Initial release.
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# BLE BTHome Battery Service
|
||||
|
||||
Broadcasts battery remaining percentage over BLE using the [BTHome protocol](https://bthome.io/) - which makes for easy integration into [Home Assistant](https://www.home-assistant.io/)
|
||||
|
||||
## Usage
|
||||
|
||||
This boot code runs in the background and has no user interface.
|
||||
|
||||
## Creator
|
||||
|
||||
[Deirdre O'Byrne](https://github.com/deirdreobyrne)
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -0,0 +1,14 @@
|
|||
var btHomeBatterySequence = 0;
|
||||
|
||||
function advertiseBTHomeBattery() {
|
||||
var advert = [0x40, 0x00, btHomeBatterySequence, 0x01, E.getBattery()];
|
||||
|
||||
require("ble_advert").set(0xFCD2, advert);
|
||||
btHomeBatterySequence = (btHomeBatterySequence + 1) & 255;
|
||||
}
|
||||
|
||||
setInterval(function() {
|
||||
advertiseBTHomeBattery();
|
||||
}, 300000); // update every 5 min
|
||||
|
||||
advertiseBTHomeBattery();
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "bootbthomebatt",
|
||||
"name": "BLE BTHome Battery Service",
|
||||
"shortName": "BTHome Battery Service",
|
||||
"version": "0.01",
|
||||
"description": "Broadcasts battery remaining over bluetooth using the BTHome protocol - makes for easy integration with Home Assistant.\n",
|
||||
"icon": "bluetooth.png",
|
||||
"type": "bootloader",
|
||||
"tags": "battery,ble,bluetooth,bthome",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"bthomebat.boot.js","url":"boot.js"}
|
||||
]
|
||||
}
|
||||
|
|
@ -3,3 +3,4 @@
|
|||
0.03: Allows showing the month in short or long format by setting `"shortMonth"` to true or false
|
||||
0.04: Improves touchscreen drag handling for background apps such as Pattern Launcher
|
||||
0.05: Fixes step count not resetting after a new day starts
|
||||
0.06 Added clockbackground app functionality
|
||||
|
|
|
|||
|
|
@ -5,11 +5,11 @@
|
|||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
let background = require("clockbg");
|
||||
let storage = require("Storage");
|
||||
let locale = require("locale");
|
||||
let widgets = require("widget_utils");
|
||||
let date = new Date();
|
||||
let bgImage;
|
||||
let configNumber = (storage.readJSON("boxclk.json", 1) || {}).selectedConfig || 0;
|
||||
let fileName = 'boxclk' + (configNumber > 0 ? `-${configNumber}` : '') + '.json';
|
||||
// Add a condition to check if the file exists, if it does not, default to 'boxclk.json'
|
||||
|
|
@ -71,14 +71,6 @@
|
|||
* ---------------------------------------------------------------
|
||||
*/
|
||||
|
||||
for (let key in boxesConfig) {
|
||||
if (key === 'bg' && boxesConfig[key].img) {
|
||||
bgImage = storage.read(boxesConfig[key].img);
|
||||
} else if (key !== 'selectedConfig') {
|
||||
boxes[key] = Object.assign({}, boxesConfig[key]);
|
||||
}
|
||||
}
|
||||
|
||||
let boxKeys = Object.keys(boxes);
|
||||
|
||||
boxKeys.forEach((key) => {
|
||||
|
|
@ -224,9 +216,7 @@
|
|||
return function(boxes) {
|
||||
date = new Date();
|
||||
g.clear();
|
||||
if (bgImage) {
|
||||
g.drawImage(bgImage, 0, 0);
|
||||
}
|
||||
background.fillRect(Bangle.appRect);
|
||||
if (boxes.time) {
|
||||
boxes.time.string = modString(boxes.time, locale.time(date, isBool(boxes.time.short, true) ? 1 : 0));
|
||||
updatePerMinute = isBool(boxes.time.short, true);
|
||||
|
|
@ -412,4 +402,4 @@
|
|||
widgets.swipeOn();
|
||||
modSetColor();
|
||||
setup();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
"version": "0.05",
|
||||
"description": "A customizable clock with configurable text boxes that can be positioned to show your favorite background",
|
||||
"icon": "app.png",
|
||||
"dependencies" : { "clockbg":"module" },
|
||||
"screenshots": [
|
||||
{"url":"screenshot.png"},
|
||||
{"url":"screenshot-1.png"},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New app!
|
||||
0.02: Advertise accelerometer data and sensor location
|
||||
0.03: Use the bleAdvert module
|
||||
0.04: Actually use the ble_advert module
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
var _a;
|
||||
{
|
||||
var __assign = Object.assign;
|
||||
var Layout_1 = require("Layout");
|
||||
|
|
@ -441,8 +440,6 @@ var _a;
|
|||
NRF.setServices(ad, {
|
||||
uart: false,
|
||||
});
|
||||
var bangle2 = Bangle;
|
||||
var cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : [];
|
||||
for (var id in ad) {
|
||||
var serv = ad[id];
|
||||
var value = void 0;
|
||||
|
|
@ -450,11 +447,7 @@ var _a;
|
|||
value = serv[ch].value;
|
||||
break;
|
||||
}
|
||||
cycle.push((_a = {}, _a[id] = value || [], _a));
|
||||
require("ble_advert").set(id, value || []);
|
||||
}
|
||||
bangle2.bleAdvert = cycle;
|
||||
NRF.setAdvertising(cycle, {
|
||||
interval: 100,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
// @ts-expect-error helper
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
|
||||
const __assign = Object.assign;
|
||||
|
||||
const Layout = require("Layout");
|
||||
|
|
@ -767,12 +767,6 @@ enableSensors();
|
|||
},
|
||||
);
|
||||
|
||||
type BleAdvert = { [key: string]: number[] };
|
||||
const bangle2 = Bangle as {
|
||||
bleAdvert?: BleAdvert | BleAdvert[];
|
||||
};
|
||||
const cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : [];
|
||||
|
||||
for(const id in ad){
|
||||
const serv = ad[id as BleServ];
|
||||
let value;
|
||||
|
|
@ -783,16 +777,7 @@ enableSensors();
|
|||
break;
|
||||
}
|
||||
|
||||
cycle.push({ [id]: value || [] });
|
||||
require("ble_advert").set(id, value || []);
|
||||
}
|
||||
|
||||
bangle2.bleAdvert = cycle;
|
||||
|
||||
NRF.setAdvertising(
|
||||
cycle,
|
||||
{
|
||||
interval: 100,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"id": "btadv",
|
||||
"name": "btadv",
|
||||
"shortName": "btadv",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth",
|
||||
"icon": "icon.png",
|
||||
"tags": "health,tool,sensors,bluetooth",
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
Set 'n' for buttons in Bangle.btHomeData correctly (avoids adding extra buttons on end of advertising)
|
||||
0.04: Fix duplicate button on edit->save
|
||||
0.05: Use the bleAdvert module
|
||||
0.06: button number can't be 0. Now generates number automatically
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "bthome",
|
||||
"name": "BTHome",
|
||||
"shortName":"BTHome",
|
||||
"version":"0.05",
|
||||
"version":"0.06",
|
||||
"description": "Allow your Bangle to advertise with BTHome and send events to Home Assistant via Bluetooth",
|
||||
"icon": "icon.png",
|
||||
"type": "app",
|
||||
|
|
|
|||
|
|
@ -11,10 +11,14 @@
|
|||
require("Storage").writeJSON("bthome.json",settings)
|
||||
}
|
||||
|
||||
// Get id number for button that is sent to bthome
|
||||
function getNewIdNumber(){
|
||||
return [1, 2, 3, 4, 5, 6, 7, 8, 9].find(id => settings.buttons.every(button => id != button.n));
|
||||
}
|
||||
|
||||
function showButtonMenu(button, isNew) {
|
||||
var isNew = false;
|
||||
if (!button) {
|
||||
button = {name:"home", icon:"home", n:0, v:"press"};
|
||||
button = {name:"home", icon:"home", n:getNewIdNumber(), v:"press"};
|
||||
isNew = true;
|
||||
}
|
||||
var actions = ["press","double_press","triple_press","long_press","long_double_press","long_triple_press"];
|
||||
|
|
@ -51,10 +55,6 @@
|
|||
format : v => actions[v],
|
||||
onchange : v => button.v=actions[v]
|
||||
},
|
||||
/*LANG*/"Button #" : {
|
||||
value : button.n, min:0, max:3,
|
||||
onchange : v => button.n=v
|
||||
},
|
||||
/*LANG*/"Save" : () => {
|
||||
if (isNew) settings.buttons.push(button);
|
||||
saveSettings();
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
0.01: New App!
|
||||
0.02: Handle the case where other apps have set bleAdvert to an array
|
||||
0.03: Use the ble_advert module
|
||||
|
|
|
|||
|
|
@ -38,21 +38,7 @@ function onTemperature(p) {
|
|||
pressure100&255,(pressure100>>8)&255,pressure100>>16
|
||||
];
|
||||
|
||||
if(Array.isArray(Bangle.bleAdvert)){
|
||||
var found = false;
|
||||
for(var ad in Bangle.bleAdvert){
|
||||
if(ad[0xFCD2]){
|
||||
ad[0xFCD2] = advert;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(!found)
|
||||
Bangle.bleAdvert.push({ 0xFCD2: advert });
|
||||
}else{
|
||||
Bangle.bleAdvert[0xFCD2] = advert;
|
||||
}
|
||||
NRF.setAdvertising(Bangle.bleAdvert);
|
||||
require("ble_advert").set(0xFCD2, advert);
|
||||
}
|
||||
|
||||
// Gets the temperature in the most accurate way with pressure sensor
|
||||
|
|
@ -60,7 +46,6 @@ function drawTemperature() {
|
|||
Bangle.getPressure().then(p =>{if (p) onTemperature(p);});
|
||||
}
|
||||
|
||||
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
|
||||
setInterval(function() {
|
||||
drawTemperature();
|
||||
}, 10000); // update every 10s
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "bthometemp",
|
||||
"name": "BTHome Temperature and Pressure",
|
||||
"shortName":"BTHome T",
|
||||
"version":"0.02",
|
||||
"version":"0.03",
|
||||
"description": "Displays temperature and pressure, and advertises them over bluetooth for Home Assistant using BTHome.io standard",
|
||||
"icon": "app.png",
|
||||
"tags": "bthome,bluetooth,temperature",
|
||||
|
|
|
|||
|
|
@ -2,3 +2,4 @@
|
|||
0.02: Hiding widgets while showing the code
|
||||
0.03: Added option to use max brightness when showing code
|
||||
0.04: Minor code improvements
|
||||
0.05: Add EAN & UPC codes
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* JS source adapted from https://github.com/lindell/JsBarcode
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
const encode = require("cards.encode.js");
|
||||
const Barcode = require("cards.Barcode.js");
|
||||
|
||||
// Standard start end and middle bits
|
||||
const SIDE_BIN = '101';
|
||||
const MIDDLE_BIN = '01010';
|
||||
|
||||
// Base class for EAN8 & EAN13
|
||||
class EAN extends Barcode {
|
||||
constructor(data, options) {
|
||||
super(data, options);
|
||||
}
|
||||
|
||||
leftText(from, to) {
|
||||
return this.text.substr(from, to);
|
||||
}
|
||||
|
||||
leftEncode(data, structure) {
|
||||
return encode(data, structure);
|
||||
}
|
||||
|
||||
rightText(from, to) {
|
||||
return this.text.substr(from, to);
|
||||
}
|
||||
|
||||
rightEncode(data, structure) {
|
||||
return encode(data, structure);
|
||||
}
|
||||
|
||||
encode() {
|
||||
const data = [
|
||||
SIDE_BIN,
|
||||
this.leftEncode(),
|
||||
MIDDLE_BIN,
|
||||
this.rightEncode(),
|
||||
SIDE_BIN
|
||||
];
|
||||
|
||||
return {
|
||||
data: data.join(''),
|
||||
text: this.text
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = EAN;
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
// Encoding documentation:
|
||||
// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Binary_encoding_of_data_digits_into_EAN-13_barcode
|
||||
/*
|
||||
* JS source adapted from https://github.com/lindell/JsBarcode
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
const EAN = require("cards.EAN.js");
|
||||
|
||||
const EAN13_STRUCTURE = [
|
||||
'LLLLLL', 'LLGLGG', 'LLGGLG', 'LLGGGL', 'LGLLGG',
|
||||
'LGGLLG', 'LGGGLL', 'LGLGLG', 'LGLGGL', 'LGGLGL'
|
||||
];
|
||||
|
||||
// Calculate the checksum digit
|
||||
// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Calculation_of_checksum_digit
|
||||
const checksum = (number) => {
|
||||
const res = number
|
||||
.substr(0, 12)
|
||||
.split('')
|
||||
.map((n) => +n)
|
||||
.reduce((sum, a, idx) => (
|
||||
idx % 2 ? sum + a * 3 : sum + a
|
||||
), 0);
|
||||
|
||||
return (10 - (res % 10)) % 10;
|
||||
};
|
||||
|
||||
class EAN13 extends EAN {
|
||||
|
||||
constructor(data, options) {
|
||||
// Add checksum if it does not exist
|
||||
if (/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)) {
|
||||
data += checksum(data);
|
||||
}
|
||||
|
||||
super(data, options);
|
||||
|
||||
// Adds a last character to the end of the barcode
|
||||
this.lastChar = options.lastChar;
|
||||
}
|
||||
|
||||
valid() {
|
||||
return (
|
||||
/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(this.data) &&
|
||||
+this.data[12] === checksum(this.data)
|
||||
);
|
||||
}
|
||||
|
||||
leftText() {
|
||||
return super.leftText(1, 6);
|
||||
}
|
||||
|
||||
leftEncode() {
|
||||
const data = this.data.substr(1, 6);
|
||||
const structure = EAN13_STRUCTURE[this.data[0]];
|
||||
return super.leftEncode(data, structure);
|
||||
}
|
||||
|
||||
rightText() {
|
||||
return super.rightText(7, 6);
|
||||
}
|
||||
|
||||
rightEncode() {
|
||||
const data = this.data.substr(7, 6);
|
||||
return super.rightEncode(data, 'RRRRRR');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = EAN13;
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
// Encoding documentation:
|
||||
// http://www.barcodeisland.com/ean8.phtml
|
||||
/*
|
||||
* JS source adapted from https://github.com/lindell/JsBarcode
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
const EAN = require("cards.EAN.js");
|
||||
|
||||
// Calculate the checksum digit
|
||||
const checksum = (number) => {
|
||||
const res = number
|
||||
.substr(0, 7)
|
||||
.split('')
|
||||
.map((n) => +n)
|
||||
.reduce((sum, a, idx) => (
|
||||
idx % 2 ? sum + a : sum + a * 3
|
||||
), 0);
|
||||
|
||||
return (10 - (res % 10)) % 10;
|
||||
};
|
||||
|
||||
class EAN8 extends EAN {
|
||||
|
||||
constructor(data, options) {
|
||||
// Add checksum if it does not exist
|
||||
if (/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)) {
|
||||
data += checksum(data);
|
||||
}
|
||||
|
||||
super(data, options);
|
||||
}
|
||||
|
||||
valid() {
|
||||
return (
|
||||
/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(this.data) &&
|
||||
+this.data[7] === checksum(this.data)
|
||||
);
|
||||
}
|
||||
|
||||
leftText() {
|
||||
return super.leftText(0, 4);
|
||||
}
|
||||
|
||||
leftEncode() {
|
||||
const data = this.data.substr(0, 4);
|
||||
return super.leftEncode(data, 'LLLL');
|
||||
}
|
||||
|
||||
rightText() {
|
||||
return super.rightText(4, 4);
|
||||
}
|
||||
|
||||
rightEncode() {
|
||||
const data = this.data.substr(4, 4);
|
||||
return super.rightEncode(data, 'RRRR');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = EAN8;
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
// Encoding documentation:
|
||||
// https://en.wikipedia.org/wiki/Universal_Product_Code#Encoding
|
||||
/*
|
||||
* JS source adapted from https://github.com/lindell/JsBarcode
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
const encode = require("cards.encode.js");
|
||||
const Barcode = require("cards.Barcode.js");
|
||||
|
||||
class UPC extends Barcode{
|
||||
constructor(data, options){
|
||||
// Add checksum if it does not exist
|
||||
if(/^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)){
|
||||
data += checksum(data);
|
||||
}
|
||||
|
||||
super(data, options);
|
||||
}
|
||||
|
||||
valid(){
|
||||
return /^[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(this.data) &&
|
||||
this.data[11] == checksum(this.data);
|
||||
}
|
||||
|
||||
encode(){
|
||||
var result = "";
|
||||
|
||||
result += "101";
|
||||
result += encode(this.data.substr(0, 6), "LLLLLL");
|
||||
result += "01010";
|
||||
result += encode(this.data.substr(6, 6), "RRRRRR");
|
||||
result += "101";
|
||||
|
||||
return {
|
||||
data: result,
|
||||
text: this.text
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Calulate the checksum digit
|
||||
// https://en.wikipedia.org/wiki/International_Article_Number_(EAN)#Calculation_of_checksum_digit
|
||||
function checksum(number){
|
||||
var result = 0;
|
||||
|
||||
var i;
|
||||
for(i = 1; i < 11; i += 2){
|
||||
result += parseInt(number[i]);
|
||||
}
|
||||
for(i = 0; i < 11; i += 2){
|
||||
result += parseInt(number[i]) * 3;
|
||||
}
|
||||
|
||||
return (10 - (result % 10)) % 10;
|
||||
}
|
||||
|
||||
module.exports = { UPC, checksum };
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
// Encoding documentation:
|
||||
// https://en.wikipedia.org/wiki/Universal_Product_Code#Encoding
|
||||
//
|
||||
// UPC-E documentation:
|
||||
// https://en.wikipedia.org/wiki/Universal_Product_Code#UPC-E
|
||||
/*
|
||||
* JS source adapted from https://github.com/lindell/JsBarcode
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
const encode = require("cards.encode.js");
|
||||
const Barcode = require("cards.Barcode.js");
|
||||
const upc = require("cards.UPC.js");
|
||||
|
||||
const EXPANSIONS = [
|
||||
"XX00000XXX",
|
||||
"XX10000XXX",
|
||||
"XX20000XXX",
|
||||
"XXX00000XX",
|
||||
"XXXX00000X",
|
||||
"XXXXX00005",
|
||||
"XXXXX00006",
|
||||
"XXXXX00007",
|
||||
"XXXXX00008",
|
||||
"XXXXX00009"
|
||||
];
|
||||
|
||||
const PARITIES = [
|
||||
["EEEOOO", "OOOEEE"],
|
||||
["EEOEOO", "OOEOEE"],
|
||||
["EEOOEO", "OOEEOE"],
|
||||
["EEOOOE", "OOEEEO"],
|
||||
["EOEEOO", "OEOOEE"],
|
||||
["EOOEEO", "OEEOOE"],
|
||||
["EOOOEE", "OEEEOO"],
|
||||
["EOEOEO", "OEOEOE"],
|
||||
["EOEOOE", "OEOEEO"],
|
||||
["EOOEOE", "OEEOEO"]
|
||||
];
|
||||
|
||||
class UPCE extends Barcode{
|
||||
constructor(data, options){
|
||||
// Code may be 6 or 8 digits;
|
||||
// A 7 digit code is ambiguous as to whether the extra digit
|
||||
// is a UPC-A check or number system digit.
|
||||
super(data, options);
|
||||
this.isValid = false;
|
||||
if(/^[0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)){
|
||||
this.middleDigits = data;
|
||||
this.upcA = expandToUPCA(data, "0");
|
||||
this.text = options.text ||
|
||||
`${this.upcA[0]}${data}${this.upcA[this.upcA.length - 1]}`;
|
||||
this.isValid = true;
|
||||
}
|
||||
else if(/^[01][0-9][0-9][0-9][0-9][0-9][0-9][0-9]$/.test(data)){
|
||||
this.middleDigits = data.substring(1, data.length - 1);
|
||||
this.upcA = expandToUPCA(this.middleDigits, data[0]);
|
||||
|
||||
if(this.upcA[this.upcA.length - 1] === data[data.length - 1]){
|
||||
this.isValid = true;
|
||||
}
|
||||
else{
|
||||
// checksum mismatch
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
valid(){
|
||||
return this.isValid;
|
||||
}
|
||||
|
||||
encode(){
|
||||
var result = "";
|
||||
|
||||
result += "101";
|
||||
result += this.encodeMiddleDigits();
|
||||
result += "010101";
|
||||
|
||||
return {
|
||||
data: result,
|
||||
text: this.text
|
||||
};
|
||||
}
|
||||
|
||||
encodeMiddleDigits() {
|
||||
const numberSystem = this.upcA[0];
|
||||
const checkDigit = this.upcA[this.upcA.length - 1];
|
||||
const parity = PARITIES[parseInt(checkDigit)][parseInt(numberSystem)];
|
||||
return encode(this.middleDigits, parity);
|
||||
}
|
||||
}
|
||||
|
||||
function expandToUPCA(middleDigits, numberSystem) {
|
||||
const lastUpcE = parseInt(middleDigits[middleDigits.length - 1]);
|
||||
const expansion = EXPANSIONS[lastUpcE];
|
||||
|
||||
let result = "";
|
||||
let digitIndex = 0;
|
||||
for(let i = 0; i < expansion.length; i++) {
|
||||
let c = expansion[i];
|
||||
if (c === 'X') {
|
||||
result += middleDigits[digitIndex++];
|
||||
} else {
|
||||
result += c;
|
||||
}
|
||||
}
|
||||
|
||||
result = `${numberSystem}${result}`;
|
||||
return `${result}${upc.checksum(result)}`;
|
||||
}
|
||||
|
||||
module.exports = UPCE;
|
||||
|
|
@ -82,16 +82,17 @@ function printSquareCode(binary, size) {
|
|||
}
|
||||
}
|
||||
function printLinearCode(binary) {
|
||||
var padding = 5;
|
||||
var yFrom = 15;
|
||||
var yTo = 28;
|
||||
var width = g.getWidth()/binary.length;
|
||||
var width = (g.getWidth()-(2*padding))/binary.length;
|
||||
for(var b = 0; b < binary.length; b++){
|
||||
var x = b * width;
|
||||
if(binary[b] === "1"){
|
||||
g.setColor(BLACK).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
|
||||
g.setColor(BLACK).fillRect({x:x+padding, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
|
||||
}
|
||||
else if(binary[b]){
|
||||
g.setColor(WHITE).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
|
||||
g.setColor(WHITE).fillRect({x:x+padding, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -132,6 +133,42 @@ function showCode(card) {
|
|||
printLinearCode(code.encode().data);
|
||||
break;
|
||||
}
|
||||
case "EAN_8": {
|
||||
g.setFont("Vector:20");
|
||||
g.setFontAlign(0,1).setColor(BLACK);
|
||||
g.drawString(card.value, g.getWidth()/2, g.getHeight());
|
||||
const EAN8 = require("cards.EAN8.js");
|
||||
let code = new EAN8(card.value, {});
|
||||
printLinearCode(code.encode().data);
|
||||
break;
|
||||
}
|
||||
case "EAN_13": {
|
||||
g.setFont("Vector:20");
|
||||
g.setFontAlign(0,1).setColor(BLACK);
|
||||
g.drawString(card.value, g.getWidth()/2, g.getHeight());
|
||||
const EAN13 = require("cards.EAN13.js");
|
||||
let code = new EAN13(card.value, {});
|
||||
printLinearCode(code.encode().data);
|
||||
break;
|
||||
}
|
||||
case "UPC_A": {
|
||||
g.setFont("Vector:20");
|
||||
g.setFontAlign(0,1).setColor(BLACK);
|
||||
g.drawString(card.value, g.getWidth()/2, g.getHeight());
|
||||
const UPC = require("cards.UPC.js");
|
||||
let code = new UPC.UPC(card.value, {});
|
||||
printLinearCode(code.encode().data);
|
||||
break;
|
||||
}
|
||||
case "UPC_E": {
|
||||
g.setFont("Vector:20");
|
||||
g.setFontAlign(0,1).setColor(BLACK);
|
||||
g.drawString(card.value, g.getWidth()/2, g.getHeight());
|
||||
const UPCE = require("cards.UPCE.js");
|
||||
let code = new UPCE(card.value, {});
|
||||
printLinearCode(code.encode().data);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
g.clear(true);
|
||||
g.setFont("Vector:30");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* JS source adapted from https://github.com/lindell/JsBarcode
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2016 Johan Lindell (johan@lindell.me)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to
|
||||
* deal in the Software without restriction, including without limitation the
|
||||
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
* sell copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
* IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
const BINARIES = {
|
||||
'L': [ // The L (left) type of encoding
|
||||
'0001101', '0011001', '0010011', '0111101', '0100011',
|
||||
'0110001', '0101111', '0111011', '0110111', '0001011'
|
||||
],
|
||||
'G': [ // The G type of encoding
|
||||
'0100111', '0110011', '0011011', '0100001', '0011101',
|
||||
'0111001', '0000101', '0010001', '0001001', '0010111'
|
||||
],
|
||||
'R': [ // The R (right) type of encoding
|
||||
'1110010', '1100110', '1101100', '1000010', '1011100',
|
||||
'1001110', '1010000', '1000100', '1001000', '1110100'
|
||||
],
|
||||
'O': [ // The O (odd) encoding for UPC-E
|
||||
'0001101', '0011001', '0010011', '0111101', '0100011',
|
||||
'0110001', '0101111', '0111011', '0110111', '0001011'
|
||||
],
|
||||
'E': [ // The E (even) encoding for UPC-E
|
||||
'0100111', '0110011', '0011011', '0100001', '0011101',
|
||||
'0111001', '0000101', '0010001', '0001001', '0010111'
|
||||
]
|
||||
};
|
||||
|
||||
// Encode data string
|
||||
const encode = (data, structure, separator) => {
|
||||
let encoded = data
|
||||
.split('')
|
||||
.map((val, idx) => BINARIES[structure[idx]])
|
||||
.map((val, idx) => val ? val[data[idx]] : '');
|
||||
|
||||
if (separator) {
|
||||
const last = data.length - 1;
|
||||
encoded = encoded.map((val, idx) => (
|
||||
idx < last ? val + separator : val
|
||||
));
|
||||
}
|
||||
|
||||
return encoded.join('');
|
||||
};
|
||||
|
||||
module.exports = encode;
|
||||
|
|
@ -1,12 +1,13 @@
|
|||
{
|
||||
"id": "cards",
|
||||
"name": "Cards",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Display loyalty cards",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}],
|
||||
"tags": "cards",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"cards.app.js","url":"app.js"},
|
||||
|
|
@ -15,6 +16,12 @@
|
|||
{"name":"cards.qrcode.js","url":"qrcode.js"},
|
||||
{"name":"cards.codabar.js","url":"codabar.js"},
|
||||
{"name":"cards.code39.js","url":"code39.js"},
|
||||
{"name":"cards.EAN.js","url":"EAN.js"},
|
||||
{"name":"cards.EAN8.js","url":"EAN8.js"},
|
||||
{"name":"cards.EAN13.js","url":"EAN13.js"},
|
||||
{"name":"cards.UPC.js","url":"UPC.js"},
|
||||
{"name":"cards.UPCE.js","url":"UPCE.js"},
|
||||
{"name":"cards.encode.js","url":"encode.js"},
|
||||
{"name":"cards.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [{"name":"cards.settings.json"}]
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
/**
|
||||
*
|
||||
*
|
||||
* A module of Geo functions for use with gps fixes
|
||||
*
|
||||
* let geo = require("geotools");
|
||||
|
|
@ -71,7 +71,7 @@ OsGridRef.latLongToOsGrid = function(point) {
|
|||
*
|
||||
*/
|
||||
function to_map_ref(digits, easting, northing) {
|
||||
if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing
|
||||
if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`);
|
||||
|
||||
let e = easting;
|
||||
let n = northing;
|
||||
|
|
@ -108,7 +108,7 @@ function to_map_ref(digits, easting, northing) {
|
|||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* Module exports section, example code below
|
||||
*
|
||||
* let geo = require("geotools");
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Remove 's' after seconds (on some clocks this looks like '5')
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
g.drawImage(atob("GBgBAP4AA/+ABwHAHABwGAAwMAAYYAAMYAAMwAAGwAAGwAAGwAAGwAAGwAAGwAAGYAAMYAAMMAAYGAAwHABwBwHAA/+AAP4AAAAA"));
|
||||
g.drawLine(11,11,x,y).drawLine(12,11,x+1,y).drawLine(11,12,x,y+1).drawLine(12,12,x+1,y+1);
|
||||
return {
|
||||
text : s.toString().padStart(2,0)+"s",
|
||||
text : s.toString().padStart(2,0),
|
||||
img : g.asImage("string")
|
||||
};
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "clkinfosec",
|
||||
"name": "Secondx Clockinfo",
|
||||
"version":"0.01",
|
||||
"name": "Seconds Clockinfo",
|
||||
"version":"0.02",
|
||||
"description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the time in seconds (many clocks only display minutes)",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
|
|
|
|||
|
|
@ -9,3 +9,6 @@
|
|||
0.09: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
||||
0.10: Use widget_utils.
|
||||
0.11: Minor code improvements
|
||||
0.12: Added setting to change Battery estimate to hours
|
||||
0.13: Fixed Battery estimate Default to percentage and improved setting string
|
||||
0.14: Use `power_usage` module
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Forum](http://forum.espruino.com/microcosms/1424/)
|
|||
|
||||
* Derived from [The Ring](https://banglejs.com/apps/?id=thering) proof of concept and the [Pastel clock](https://banglejs.com/apps/?q=pastel)
|
||||
* Includes the [Lazybones](https://banglejs.com/apps/?q=lazybones) Idle warning timer
|
||||
* Touch the top right/top left to cycle through the info display (Day, Date, Steps, Sunrise, Sunset, Heart Rate)
|
||||
* Touch the top right/top left to cycle through the info display (Day, Date, Steps, Sunrise, Sunset, Heart Rate, Battery Estimate)
|
||||
* The heart rate monitor is turned on only when Heart rate is selected and will take a few seconds to settle
|
||||
* The heart value is displayed in RED if the confidence value is less than 50%
|
||||
* NOTE: The heart rate monitor of Bangle JS 2 is not very accurate when moving about.
|
||||
|
|
@ -20,6 +20,7 @@ See [#1248](https://github.com/espruino/BangleApps/issues/1248)
|
|||
[MyLocation](https://banglejs.com/apps/?id=mylocation)
|
||||
* The screen is updated every minute to save battery power
|
||||
* Uses the [BloggerSansLight](https://www.1001fonts.com/rounded-fonts.html?page=3) font, which if free for commercial use
|
||||
* You need to run >2V22 to show the battery estimate in hours
|
||||
|
||||
## Future Development
|
||||
* Use mini icons in the information line rather that text
|
||||
|
|
|
|||
|
|
@ -83,6 +83,7 @@ function loadSettings() {
|
|||
settings.gy = settings.gy||'#020';
|
||||
settings.fg = settings.fg||'#0f0';
|
||||
settings.idle_check = (settings.idle_check === undefined ? true : settings.idle_check);
|
||||
settings.batt_hours = (settings.batt_hours === undefined ? false : settings.batt_hours);
|
||||
assignPalettes();
|
||||
}
|
||||
|
||||
|
|
@ -112,13 +113,39 @@ function updateSunRiseSunSet(now, lat, lon, line){
|
|||
sunSet = extractTime(times.sunset);
|
||||
}
|
||||
|
||||
function batteryString(){
|
||||
let stringToInsert;
|
||||
if (settings.batt_hours) {
|
||||
var batt_usage = require("power_usage").get().hrsLeft;
|
||||
let rounded;
|
||||
if (batt_usage > 24) {
|
||||
var days = Math.floor(batt_usage/24);
|
||||
var hours = Math.round((batt_usage/24 - days) * 24);
|
||||
stringToInsert = '\n' + days + ((days < 2) ? 'd' : 'ds') + ' ' + hours + ((hours < 2) ? 'h' : 'hs');
|
||||
}
|
||||
else if (batt_usage > 9) {
|
||||
rounded = Math.round(200000/E.getPowerUsage().total * 10) / 10;
|
||||
}
|
||||
else {
|
||||
rounded = Math.round(200000/E.getPowerUsage().total * 100) / 100;
|
||||
}
|
||||
if (batt_usage < 24) {
|
||||
stringToInsert = '\n' + rounded + ' ' + ((batt_usage < 2) ? 'h' : 'hs');
|
||||
}
|
||||
}
|
||||
else{
|
||||
stringToInsert = ' ' + E.getBattery() + '%';
|
||||
}
|
||||
return 'BATTERY' + stringToInsert;
|
||||
}
|
||||
|
||||
const infoData = {
|
||||
ID_DATE: { calc: () => {var d = (new Date()).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} },
|
||||
ID_DAY: { calc: () => {var d = require("locale").dow(new Date()).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} },
|
||||
ID_SR: { calc: () => 'SUNRISE ' + sunRise },
|
||||
ID_SS: { calc: () => 'SUNSET ' + sunSet },
|
||||
ID_STEP: { calc: () => 'STEPS ' + getSteps() },
|
||||
ID_BATT: { calc: () => 'BATTERY ' + E.getBattery() + '%' },
|
||||
ID_BATT: { calc: batteryString},
|
||||
ID_HRM: { calc: () => hrmCurrent }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "daisy",
|
||||
"name": "Daisy",
|
||||
"version": "0.11",
|
||||
"version": "0.14",
|
||||
"dependencies": {"mylocation":"app"},
|
||||
"description": "A beautiful digital clock with large ring guage, idle timer and a cyclic information line that includes, day, date, steps, battery, sunrise and sunset times",
|
||||
"icon": "app.png",
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
let s = {'gy' : '#020',
|
||||
'fg' : '#0f0',
|
||||
'color': 'Green',
|
||||
'check_idle' : true};
|
||||
'check_idle' : true,
|
||||
'batt_hours' : false};
|
||||
|
||||
// ...and overwrite them with any saved values
|
||||
// This way saved values are preserved if a new version adds more settings
|
||||
|
|
@ -45,6 +46,14 @@
|
|||
s.idle_check = v;
|
||||
save();
|
||||
},
|
||||
},
|
||||
'Expected Battery Life In Days Not Percentage': {
|
||||
value: !!s.batt_hours,
|
||||
onchange: v => {
|
||||
s.batt_hours = v;
|
||||
save();
|
||||
},
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New drag/swipe date time picker, e.g. for use with dated events alarms
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
# App Name
|
||||
|
||||
Datetime Picker allows to swipe along the bars to select date and time elements, e.g. for the datetime of Events in the Alarm App.
|
||||
|
||||
Screenshot: 
|
||||
|
||||
## Controls
|
||||
|
||||
Swipe to increase or decrease date and time elements. Press button or go back to select shown datetime.
|
||||
|
||||

|
||||
|
||||
1. Year: swipe up to increase, down to decrease
|
||||
2. Month: swipe right to increase, left to decrease
|
||||
3. Day: swipe up to increase, down to decrease
|
||||
4. Week: swipe up to increase week (same day next week), down to decrease (same day previous week)
|
||||
5. Weekday: swipe right to increase, left to decrease (basically the same effect as 3, but with a focus on the weekday)
|
||||
6. Hour: swipe right to increase, left to decrease
|
||||
7. Minutes: swipe right to increase, left to decrease
|
||||
8. 15 minutes: 00, 15, 30 or 45 minutes; swipe up to increase, down to decrease; wrap-around i.e. goes back to 00 after increasing from 45
|
||||
|
||||
## How to use it in code
|
||||
|
||||
Sample code which would show a prompt with the number of days and hours between now and the selected datetime:
|
||||
|
||||
require("datetimeinput").input().then(result => {
|
||||
E.showPrompt(`${result}\n\n${require("time_utils").formatDuration(Math.abs(result-Date.now()))}`, {buttons:{"Ok":true}}).then(function() {
|
||||
load();
|
||||
});
|
||||
});
|
||||
|
||||
To set the initial value, pass a Date object named _datetime_, e.g. for today at 9:30 :
|
||||
|
||||
var datetime = new Date();
|
||||
datetime.setHours(9, 30);
|
||||
require("datetimeinput").input({datetime}).then(...
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwgP/AAnfAgf9z4FD/AFE/gFECIoFB98+tv+voFB//C/99z3Z7+J84XC3/7DpAFhKYP3AgP3AoPAOQMD/v/84LB+Z2FABiDKPoqJFKaWe/P/9Pznuf+wKB/29z+2//uTYOeTYPtRMxZKQaPAh6hBnEBwEGAoMYgHf9+/dwP5A=="))
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
require("datetimeinput").input().then(result => {
|
||||
E.showPrompt(`${result}\n\n${require("time_utils").formatDuration(Math.abs(result-Date.now()))}`, {buttons:{"Ok":true}}).then(function() {
|
||||
load();
|
||||
});
|
||||
});
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
|
|
@ -0,0 +1,145 @@
|
|||
exports.input = function(options) {
|
||||
options = options||{};
|
||||
var selectedDate;
|
||||
if (options.datetime instanceof Date) {
|
||||
selectedDate = new Date(options.datetime.getTime());
|
||||
} else {
|
||||
selectedDate = new Date();
|
||||
selectedDate.setMinutes(0);
|
||||
selectedDate.setSeconds(0);
|
||||
selectedDate.setMilliseconds(0);
|
||||
selectedDate.setHours(selectedDate.getHours() + 1);
|
||||
}
|
||||
|
||||
var R;
|
||||
var tip = {w: 12, h: 10};
|
||||
var arrowRectArray;
|
||||
var dragging = null;
|
||||
var startPos = null;
|
||||
var dateAtDragStart = null;
|
||||
var SELECTEDFONT = '6x8:2';
|
||||
|
||||
function drawDateTime() {
|
||||
g.clearRect(R.x+tip.w,R.y,R.x2-tip.w,R.y+40);
|
||||
g.clearRect(R.x+tip.w,R.y2-60,R.x2-tip.w,R.y2-40);
|
||||
|
||||
g.setFont(SELECTEDFONT).setColor(g.theme.fg).setFontAlign(-1, -1, 0);
|
||||
var dateUtils = require('date_utils');
|
||||
g.drawString(selectedDate.getFullYear(), R.x+tip.w+10, R.y+15)
|
||||
.drawString(dateUtils.month(selectedDate.getMonth()+1,1), R.x+tip.w+65, R.y+15)
|
||||
.drawString(selectedDate.getDate(), R.x2-tip.w-40, R.y+15)
|
||||
.drawString(`${dateUtils.dow(selectedDate.getDay(), 1)} ${selectedDate.toLocalISOString().slice(11,16)}`, R.x+tip.w+10, R.y2-60);
|
||||
}
|
||||
|
||||
let dragHandler = function(event) {
|
||||
"ram";
|
||||
|
||||
if (event.b) {
|
||||
if (dragging === null) {
|
||||
// determine which component we are affecting
|
||||
var rect = arrowRectArray.find(rect => rect.y2
|
||||
? (event.y >= rect.y && event.y <= rect.y2 && event.x >= rect.x - 10 && event.x <= rect.x + tip.w + 10)
|
||||
: (event.x >= rect.x && event.x <= rect.x2 && event.y >= rect.y - tip.w - 5 && event.y <= rect.y + 5));
|
||||
if (rect) {
|
||||
dragging = rect;
|
||||
startPos = dragging.y2 ? event.y : event.x;
|
||||
dateAtDragStart = selectedDate;
|
||||
}
|
||||
}
|
||||
|
||||
if (dragging) {
|
||||
dragging.swipe(dragging.y2 ? startPos - event.y : event.x - startPos);
|
||||
drawDateTime();
|
||||
}
|
||||
} else {
|
||||
dateAtDragStart = null;
|
||||
dragging = null;
|
||||
startPos = null;
|
||||
}
|
||||
};
|
||||
|
||||
let catchSwipe = ()=>{
|
||||
E.stopEventPropagation&&E.stopEventPropagation();
|
||||
};
|
||||
|
||||
return new Promise((resolve,reject) => {
|
||||
// Interpret touch input
|
||||
Bangle.setUI({
|
||||
mode: 'custom',
|
||||
back: ()=>{
|
||||
Bangle.setUI();
|
||||
Bangle.prependListener&&Bangle.removeListener('swipe', catchSwipe); // Remove swipe listener if it was added with `Bangle.prependListener()` (fw2v19 and up).
|
||||
g.clearRect(Bangle.appRect);
|
||||
resolve(selectedDate);
|
||||
},
|
||||
drag: dragHandler
|
||||
});
|
||||
Bangle.prependListener&&Bangle.prependListener('swipe', catchSwipe); // Intercept swipes on fw2v19 and later. Should not break on older firmwares.
|
||||
|
||||
R = Bangle.appRect;
|
||||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
function drawArrow(rect) {
|
||||
if(rect.x2) {
|
||||
g.fillRect(rect.x + tip.h, rect.y - tip.w + 4, rect.x2 - tip.h, rect.y - 4)
|
||||
.fillPoly([rect.x + tip.h, rect.y, rect.x + tip.h, rect.y - tip.w, rect.x, rect.y - (tip.w / 2)])
|
||||
.fillPoly([rect.x2-tip.h, rect.y, rect.x2 - tip.h, rect.y - tip.w, rect.x2, rect.y - (tip.w / 2)]);
|
||||
} else {
|
||||
g.fillRect(rect.x + 4, rect.y + tip.h, rect.x + tip.w - 4, rect.y2 - tip.h)
|
||||
.fillPoly([rect.x, rect.y + tip.h, rect.x + tip.w, rect.y + tip.h, rect.x + (tip.w / 2), rect.y])
|
||||
.fillPoly([rect.x, rect.y2 - tip.h, rect.x + tip.w, rect.y2 - tip.h, rect.x + (tip.w / 2), rect.y2]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var yearArrowRect = {x: R.x, y: R.y, y2: R.y + (R.y2 - R.y) * 0.4, swipe: d => {
|
||||
selectedDate = new Date(dateAtDragStart.valueOf());
|
||||
selectedDate.setFullYear(dateAtDragStart.getFullYear() + Math.floor(d/10));
|
||||
if (dateAtDragStart.getDate() != selectedDate.getDate()) selectedDate.setDate(0);
|
||||
}};
|
||||
|
||||
var weekArrowRect = {x: R.x, y: yearArrowRect.y2 + 10, y2: R.y2 - tip.w - 5, swipe: d => {
|
||||
selectedDate = new Date(dateAtDragStart.valueOf());
|
||||
selectedDate.setDate(dateAtDragStart.getDate() + (Math.floor(d/10) * 7));
|
||||
}};
|
||||
|
||||
var dayArrowRect = {x: R.x2 - tip.w, y: R.y, y2: R.y + (R.y2 - R.y) * 0.4, swipe: d => {
|
||||
selectedDate = new Date(dateAtDragStart.valueOf());
|
||||
selectedDate.setDate(dateAtDragStart.getDate() + Math.floor(d/10));
|
||||
}};
|
||||
|
||||
var fifteenMinutesArrowRect = {x: R.x2 - tip.w, y: dayArrowRect.y2 + 10, y2: R.y2 - tip.w - 5, swipe: d => {
|
||||
selectedDate = new Date(dateAtDragStart.valueOf());
|
||||
selectedDate.setMinutes((((dateAtDragStart.getMinutes() - (dateAtDragStart.getMinutes() % 15) + (Math.floor(d/14) * 15)) % 60) + 60) % 60);
|
||||
}};
|
||||
|
||||
var weekdayArrowRect = {x: R.x, y: R.y2, x2: (R.x2 - R.x) * 0.3 - 5, swipe: d => {
|
||||
selectedDate = new Date(dateAtDragStart.valueOf());
|
||||
selectedDate.setDate(dateAtDragStart.getDate() + Math.floor(d/10));
|
||||
}};
|
||||
|
||||
var hourArrowRect = {x: weekdayArrowRect.x2 + 5, y: R.y2, x2: weekdayArrowRect.x2 + (R.x2 - R.x) * 0.38, swipe: d => {
|
||||
selectedDate = new Date(dateAtDragStart.valueOf());
|
||||
selectedDate.setHours((((dateAtDragStart.getHours() + Math.floor(d/10)) % 24) + 24) % 24);
|
||||
}};
|
||||
|
||||
var minutesArrowRect = {x: hourArrowRect.x2 + 5, y: R.y2, x2: R.x2, swipe: d => {
|
||||
selectedDate = new Date(dateAtDragStart.valueOf());
|
||||
selectedDate.setMinutes((((dateAtDragStart.getMinutes() + Math.floor(d/7)) % 60) + 60) % 60);
|
||||
}};
|
||||
|
||||
var monthArrowRect = {x: (R.x2 - R.x) * 0.2, y: R.y2 / 2 + 5, x2: (R.x2 - R.x) * 0.8, swipe: d => {
|
||||
selectedDate = new Date(dateAtDragStart.valueOf());
|
||||
selectedDate.setMonth(dateAtDragStart.getMonth() + Math.floor(d/10));
|
||||
if (dateAtDragStart.getDate() != selectedDate.getDate()) selectedDate.setDate(0);
|
||||
}};
|
||||
|
||||
arrowRectArray = [yearArrowRect, weekArrowRect, dayArrowRect, fifteenMinutesArrowRect,
|
||||
weekdayArrowRect, hourArrowRect, minutesArrowRect, monthArrowRect];
|
||||
|
||||
drawDateTime();
|
||||
arrowRectArray.forEach(drawArrow);
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
{ "id": "datetime_picker",
|
||||
"name": "Datetime picker",
|
||||
"shortName":"Datetime picker",
|
||||
"version":"0.01",
|
||||
"description": "A library that allows to pick a date and time by swiping.",
|
||||
"icon":"app.png",
|
||||
"type":"module",
|
||||
"tags":"datetimeinput",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"provides_modules" : ["datetimeinput"],
|
||||
"readme": "README.md",
|
||||
"screenshots" : [ { "url":"screenshot.png" } ],
|
||||
"storage": [
|
||||
{"name":"datetimeinput","url":"lib.js"},
|
||||
{"name":"datetime_picker.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
|
@ -0,0 +1 @@
|
|||
0.01: attempt to import
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# Ded Reckon
|
||||
|
||||
Dead Reckoning using compass and step counter.
|
||||
|
||||
This allows logging track using "dead reckoning" -- that's logging
|
||||
angles from compass and distances from step counter. You need to mark
|
||||
turns, and point watch to direction of the turn. Simultaneously, it
|
||||
tries to log positions using GPS. You can use it to calibrate your
|
||||
step length by comparing GPS and step counter data. It can also get
|
||||
pretty accurate recording of track walked in right circumstances.
|
||||
|
||||
Tap bottom part of the screen to select display (text or map for
|
||||
now). Point watch to new direction, then tap top left part of screen
|
||||
to indicate a turn.
|
||||
|
||||
Map shows blue line for track from dead reckonging, and green line for
|
||||
track from GPS.
|
||||
|
||||
You probably want magnav installed (and calibrated) for useful
|
||||
results, as it provides library with better compass.
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwhHXAH4A/AH4A/AFsAFtoADF1wwqF4wwhEI5goGGIjFYN4wFF1KbHGUolIMc4lGSdIwJd9DstAH7FrBywwgad4veDwojJBIIvcFwIACGBYICGDYvEGBYvdFwqyLL8i+LF7oxFRxgveGAQ0EF5IwfMY4vpL5AFLAEYv/F8owoE44vrAY4vmAQIEEF85dGGE0AE4gvoFwpmHd0oINAH4A/AH4AvA"))
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
|
|
@ -0,0 +1,442 @@
|
|||
/* Ded Reckon */
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
/* fmt library v0.1.3 */
|
||||
let fmt = {
|
||||
icon_alt : "\0\x08\x1a\1\x00\x00\x00\x20\x30\x78\x7C\xFE\xFF\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
icon_m : "\0\x08\x1a\1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
icon_km : "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
icon_kph : "\0\x08\x1a\1\xC3\xC6\xCC\xD8\xF0\xD8\xCC\xC6\xC3\x00\xC3\xE7\xFF\xDB\xC3\xC3\xC3\xC3\x00\xFF\x00\xC3\xC3\xFF\xC3\xC3",
|
||||
icon_c : "\0\x08\x1a\1\x00\x00\x60\x90\x90\x60\x00\x7F\xFF\xC0\xC0\xC0\xC0\xC0\xFF\x7F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||
|
||||
/* 0 .. DD.ddddd
|
||||
1 .. DD MM.mmm'
|
||||
2 .. DD MM'ss"
|
||||
*/
|
||||
geo_mode : 1,
|
||||
|
||||
init: function() {},
|
||||
fmtDist: function(km) {
|
||||
if (km >= 1.0) return km.toFixed(1) + this.icon_km;
|
||||
return (km*1000).toFixed(0) + this.icon_m;
|
||||
},
|
||||
fmtSteps: function(n) { return this.fmtDist(0.001 * 0.719 * n); },
|
||||
fmtAlt: function(m) { return m.toFixed(0) + this.icon_alt; },
|
||||
draw_dot : 1,
|
||||
add0: function(i) {
|
||||
if (i > 9) {
|
||||
return ""+i;
|
||||
} else {
|
||||
return "0"+i;
|
||||
}
|
||||
},
|
||||
fmtTOD: function(now) {
|
||||
this.draw_dot = !this.draw_dot;
|
||||
let dot = ":";
|
||||
if (!this.draw_dot)
|
||||
dot = ".";
|
||||
return now.getHours() + dot + this.add0(now.getMinutes());
|
||||
},
|
||||
fmtNow: function() { return this.fmtTOD(new Date()); },
|
||||
fmtTimeDiff: function(d) {
|
||||
if (d < 180)
|
||||
return ""+d.toFixed(0);
|
||||
d = d/60;
|
||||
return ""+d.toFixed(0)+"m";
|
||||
},
|
||||
fmtAngle: function(x) {
|
||||
switch (this.geo_mode) {
|
||||
case 0:
|
||||
return "" + x;
|
||||
case 1: {
|
||||
let d = Math.floor(x);
|
||||
let m = x - d;
|
||||
m = m*60;
|
||||
return "" + d + " " + m.toFixed(3) + "'";
|
||||
}
|
||||
case 2: {
|
||||
let d = Math.floor(x);
|
||||
let m = x - d;
|
||||
m = m*60;
|
||||
let mf = Math.floor(m);
|
||||
let s = m - mf;
|
||||
s = s*60;
|
||||
return "" + d + " " + mf + "'" + s.toFixed(0) + '"';
|
||||
}
|
||||
}
|
||||
return "bad mode?";
|
||||
},
|
||||
fmtPos: function(pos) {
|
||||
let x = pos.lat;
|
||||
let c = "N";
|
||||
if (x<0) {
|
||||
c = "S";
|
||||
x = -x;
|
||||
}
|
||||
let s = c+this.fmtAngle(x) + "\n";
|
||||
c = "E";
|
||||
if (x<0) {
|
||||
c = "W";
|
||||
x = -x;
|
||||
}
|
||||
return s + c + this.fmtAngle(x);
|
||||
},
|
||||
fmtFix: function(fix, t) {
|
||||
if (fix && fix.fix && fix.lat) {
|
||||
return this.fmtSpeed(fix.speed) + " " +
|
||||
this.fmtAlt(fix.alt);
|
||||
} else {
|
||||
return "N/FIX " + this.fmtTimeDiff(t);
|
||||
}
|
||||
},
|
||||
fmtSpeed: function(kph) {
|
||||
return kph.toFixed(1) + this.icon_kph;
|
||||
},
|
||||
};
|
||||
|
||||
/* gps library v0.1.1 */
|
||||
let gps = {
|
||||
emulator: -1,
|
||||
init: function(x) {
|
||||
this.emulator = (process.env.BOARD=="EMSCRIPTEN"
|
||||
|| process.env.BOARD=="EMSCRIPTEN2")?1:0;
|
||||
},
|
||||
state: {},
|
||||
on_gps: function(f) {
|
||||
let fix = this.getGPSFix();
|
||||
f(fix);
|
||||
|
||||
/*
|
||||
"lat": number, // Latitude in degrees
|
||||
"lon": number, // Longitude in degrees
|
||||
"alt": number, // altitude in M
|
||||
"speed": number, // Speed in kph
|
||||
"course": number, // Course in degrees
|
||||
"time": Date, // Current Time (or undefined if not known)
|
||||
"satellites": 7, // Number of satellites
|
||||
"fix": 1 // NMEA Fix state - 0 is no fix
|
||||
"hdop": number, // Horizontal Dilution of Precision
|
||||
*/
|
||||
this.state.timeout = setTimeout(this.on_gps, 1000, f);
|
||||
},
|
||||
off_gps: function() {
|
||||
clearTimeout(this.state.timeout);
|
||||
},
|
||||
getGPSFix: function() {
|
||||
if (!this.emulator)
|
||||
return Bangle.getGPSFix();
|
||||
let fix = {};
|
||||
fix.fix = 1;
|
||||
fix.lat = 50;
|
||||
fix.lon = 14-(getTime()-this.gps_start) / 1000; /* Go West! */
|
||||
fix.alt = 200;
|
||||
fix.speed = 5;
|
||||
fix.course = 30;
|
||||
fix.time = Date();
|
||||
fix.satellites = 5;
|
||||
fix.hdop = 12;
|
||||
return fix;
|
||||
},
|
||||
gps_start : -1,
|
||||
start_gps: function() {
|
||||
Bangle.setGPSPower(1, "libgps");
|
||||
this.gps_start = getTime();
|
||||
},
|
||||
stop_gps: function() {
|
||||
Bangle.setGPSPower(0, "libgps");
|
||||
},
|
||||
};
|
||||
|
||||
/* ui library 0.1 */
|
||||
let ui = {
|
||||
display: 0,
|
||||
numScreens: 2,
|
||||
drawMsg: function(msg) {
|
||||
g.reset().setFont("Vector", 35)
|
||||
.setColor(1,1,1)
|
||||
.fillRect(0, this.wi, 176, 176)
|
||||
.setColor(0,0,0)
|
||||
.drawString(msg, 5, 30);
|
||||
},
|
||||
drawBusy: function() {
|
||||
this.drawMsg("\n.oO busy");
|
||||
},
|
||||
nextScreen: function() {
|
||||
print("nextS");
|
||||
this.display = this.display + 1;
|
||||
if (this.display == this.numScreens)
|
||||
this.display = 0;
|
||||
this.drawBusy();
|
||||
},
|
||||
prevScreen: function() {
|
||||
print("prevS");
|
||||
this.display = this.display - 1;
|
||||
if (this.display < 0)
|
||||
this.display = this.numScreens - 1;
|
||||
this.drawBusy();
|
||||
},
|
||||
onSwipe: function(dir) {
|
||||
this.nextScreen();
|
||||
},
|
||||
h: 176,
|
||||
w: 176,
|
||||
wi: 32,
|
||||
last_b: 0,
|
||||
touchHandler: function(d) {
|
||||
let x = Math.floor(d.x);
|
||||
let y = Math.floor(d.y);
|
||||
|
||||
if (d.b != 1 || this.last_b != 0) {
|
||||
this.last_b = d.b;
|
||||
return;
|
||||
}
|
||||
|
||||
print("touch", x, y, this.h, this.w);
|
||||
|
||||
/*
|
||||
if ((x<this.h/2) && (y<this.w/2)) {
|
||||
}
|
||||
if ((x>this.h/2) && (y<this.w/2)) {
|
||||
}
|
||||
*/
|
||||
|
||||
if ((x<this.h/2) && (y>this.w/2)) {
|
||||
print("prev");
|
||||
this.prevScreen();
|
||||
}
|
||||
if ((x>this.h/2) && (y>this.w/2)) {
|
||||
print("next");
|
||||
this.nextScreen();
|
||||
}
|
||||
},
|
||||
init: function() {
|
||||
}
|
||||
};
|
||||
|
||||
var last_steps = Bangle.getStepCount(), last_time = getTime(), speed = 0, step_phase = 0;
|
||||
|
||||
var mpstep = 0.719 * 1.15;
|
||||
|
||||
function updateSteps() {
|
||||
if (step_phase ++ > 9) {
|
||||
step_phase =0;
|
||||
let steps = Bangle.getStepCount();
|
||||
let time = getTime();
|
||||
|
||||
speed = 3.6 * mpstep * ((steps-last_steps) / (time-last_time));
|
||||
last_steps = steps;
|
||||
last_time = time;
|
||||
}
|
||||
return "" + fmt.fmtSpeed(speed) + " " + step_phase + "\n" + fmt.fmtDist(log_dist/1000) + " " + fmt.fmtDist(log_last/1000);
|
||||
}
|
||||
|
||||
/* compensated compass */
|
||||
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
|
||||
const tiltfixread = require("magnav").tiltfixread;
|
||||
var heading;
|
||||
|
||||
|
||||
var cancel_gps = false;
|
||||
|
||||
function drawStats() {
|
||||
let fix = gps.getGPSFix();
|
||||
|
||||
let msg = fmt.fmtFix(fix, getTime() - gps.gps_start);
|
||||
|
||||
msg += "\n" + fmt.fmtDist(gps_dist/1000) + " " + fmt.fmtDist(gps_last/1000) + "\n" + updateSteps();
|
||||
let c = Bangle.getCompass();
|
||||
if (c) msg += "\n" + c.heading.toFixed(0) + "/" + heading.toFixed(0) + "deg " + log.length + "\n";
|
||||
|
||||
g.reset().clear().setFont("Vector", 31)
|
||||
.setColor(1,1,1)
|
||||
.fillRect(0, 24, 176, 100)
|
||||
.setColor(0,0,0)
|
||||
.drawString(msg, 3, 25);
|
||||
}
|
||||
|
||||
function updateGps() {
|
||||
if (cancel_gps)
|
||||
return;
|
||||
heading = tiltfixread(CALIBDATA.offset,CALIBDATA.scale);
|
||||
if (ui.display == 0) {
|
||||
setTimeout(updateGps, 1000);
|
||||
drawLog();
|
||||
drawStats();
|
||||
}
|
||||
if (ui.display == 1) {
|
||||
setTimeout(updateGps, 1000);
|
||||
drawLog();
|
||||
}
|
||||
}
|
||||
|
||||
function stopGps() {
|
||||
cancel_gps=true;
|
||||
gps.stop_gps();
|
||||
}
|
||||
|
||||
var log = [], log_dist = 0, gps_dist = 0;
|
||||
var log_last = 0, gps_last = 0;
|
||||
|
||||
function logEntry() {
|
||||
let e = {};
|
||||
e.time = getTime();
|
||||
e.fix = gps.getGPSFix();
|
||||
e.steps = Bangle.getStepCount();
|
||||
if (0) {
|
||||
let c = Bangle.getCompass();
|
||||
if (c)
|
||||
e.dir = c.heading;
|
||||
else
|
||||
e.dir = -1;
|
||||
} else {
|
||||
e.dir = heading;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
function onTurn() {
|
||||
let e = logEntry();
|
||||
log.push(e);
|
||||
}
|
||||
|
||||
function radians(a) { return a*Math.PI/180; }
|
||||
function degrees(a) { return a*180/Math.PI; }
|
||||
// distance between 2 lat and lons, in meters, Mean Earth Radius = 6371km
|
||||
// https://www.movable-type.co.uk/scripts/latlong.html
|
||||
// (Equirectangular approximation)
|
||||
function calcDistance(a,b) {
|
||||
var x = radians(b.lon-a.lon) * Math.cos(radians((a.lat+b.lat)/2));
|
||||
var y = radians(b.lat-a.lat);
|
||||
return Math.sqrt(x*x + y*y) * 6371000;
|
||||
}
|
||||
|
||||
var dn, de;
|
||||
function initConv(fix) {
|
||||
let n = { lat: fix.lat+1, lon: fix.lon };
|
||||
let e = { lat: fix.lat, lon: fix.lon+1 };
|
||||
|
||||
dn = calcDistance(fix, n);
|
||||
de = calcDistance(fix, e);
|
||||
print("conversion is ", dn, 108000, de, 50000);
|
||||
}
|
||||
function toM(start, fix) {
|
||||
return { x: (fix.lon - start.lon) * de, y: (fix.lat - start.lat) * dn };
|
||||
}
|
||||
var mpp = 4;
|
||||
function toPix(q) {
|
||||
let p = { x: q.x, y: q.y };
|
||||
p.x /= mpp; /* 10 m / pix */
|
||||
p.y /= -mpp;
|
||||
p.x += 85;
|
||||
p.y += 85;
|
||||
return p;
|
||||
}
|
||||
|
||||
function drawLog() {
|
||||
let here = logEntry();
|
||||
if (!here.fix.lat) {
|
||||
here.fix.lat = 50;
|
||||
here.fix.lon = 14;
|
||||
}
|
||||
initConv(here.fix);
|
||||
log.push(here);
|
||||
let l = log;
|
||||
log_dist = 0;
|
||||
log_last = -1;
|
||||
gps_last = -1;
|
||||
|
||||
g.reset().clear();
|
||||
g.setColor(0, 0, 1);
|
||||
let last = { x: 0, y: 0 };
|
||||
for (let i = l.length - 2; i >= 0; i--) {
|
||||
let next = {};
|
||||
let m = (l[i+1].steps - l[i].steps) * mpstep;
|
||||
let dir = radians(180 + l[i].dir);
|
||||
next.x = last.x + m * Math.sin(dir);
|
||||
next.y = last.y + m * Math.cos(dir);
|
||||
print(dir, m, last, next);
|
||||
let lp = toPix(last);
|
||||
let np = toPix(next);
|
||||
g.drawLine(lp.x, lp.y, np.x, np.y);
|
||||
g.drawCircle(np.x, np.y, 3);
|
||||
last = next;
|
||||
if (log_last == -1)
|
||||
log_last = m;
|
||||
log_dist += m;
|
||||
}
|
||||
g.setColor(0, 1, 0);
|
||||
last = { x: 0, y: 0 };
|
||||
gps_dist = 0;
|
||||
for (let i = l.length - 2; i >= 0; i--) {
|
||||
let fix = l[i].fix;
|
||||
if (fix.fix && fix.lat) {
|
||||
let next = toM(here.fix, fix);
|
||||
let lp = toPix(last);
|
||||
let np = toPix(next);
|
||||
let d = Math.sqrt((next.x-last.x)*(next.x-last.x)+(next.y-last.y)*(next.y-last.y));
|
||||
if (gps_last == -1)
|
||||
gps_last = d;
|
||||
gps_dist += d;
|
||||
g.drawLine(lp.x, lp.y, np.x, np.y);
|
||||
g.drawCircle(np.x, np.y, 3);
|
||||
last = next;
|
||||
}
|
||||
}
|
||||
log.pop();
|
||||
}
|
||||
|
||||
function testPaint() {
|
||||
let pos = gps.getGPSFix();
|
||||
log = [];
|
||||
let e = { fix: pos, steps: 100, dir: 0 };
|
||||
log.push(e);
|
||||
e = { fix: pos, steps: 200, dir: 90 };
|
||||
log.push(e);
|
||||
e = { fix: pos, steps: 300, dir: 0 };
|
||||
log.push(e);
|
||||
print(log, log.length, log[0], log[1]);
|
||||
drawLog();
|
||||
}
|
||||
|
||||
function touchHandler(d) {
|
||||
let x = Math.floor(d.x);
|
||||
let y = Math.floor(d.y);
|
||||
|
||||
if (d.b != 1 || ui.last_b != 0) {
|
||||
ui.last_b = d.b;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if ((x<ui.h/2) && (y<ui.w/2)) {
|
||||
ui.drawMsg("Turn");
|
||||
onTurn();
|
||||
}
|
||||
if ((x>ui.h/2) && (y<ui.w/2)) {
|
||||
ui.drawMsg("Writing");
|
||||
require('Storage').writeJSON("speedstep."+getTime()+".json", log);
|
||||
ui.drawMsg("Wrote");
|
||||
}
|
||||
ui.touchHandler(d);
|
||||
}
|
||||
|
||||
|
||||
fmt.init();
|
||||
gps.init();
|
||||
ui.init();
|
||||
ui.drawBusy();
|
||||
gps.start_gps();
|
||||
Bangle.setCompassPower(1, "speedstep");
|
||||
Bangle.on("drag", touchHandler);
|
||||
Bangle.setUI({
|
||||
mode : "custom",
|
||||
swipe : (s) => ui.onSwipe(s),
|
||||
clock : 0
|
||||
});
|
||||
|
||||
if (0)
|
||||
testPaint();
|
||||
if (1) {
|
||||
g.reset();
|
||||
updateGps();
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{ "id": "dedreckon",
|
||||
"name": "Ded Reckon",
|
||||
"version": "0.01",
|
||||
"description": "Dead Reckoning using compass and step counter",
|
||||
"icon": "app.png",
|
||||
"readme": "README.md",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"tags": "outdoors",
|
||||
"storage": [
|
||||
{"name":"dedreckon.app.js","url":"dedreckon.app.js"},
|
||||
{"name":"dedreckon.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Delayed Locking
|
||||
|
||||
Delay the locking of the touchscreen to 5 seconds after the backlight turns off. Giving you the chance to interact with the watch without having to press the hardware button again.
|
||||
|
||||
## Usage
|
||||
|
||||
Just install and the behavior is tweaked at boot time.
|
||||
|
||||
## Features
|
||||
|
||||
- respects the LCD Timeout and Brightness as configured in the settings app.
|
||||
|
||||
## Requests
|
||||
|
||||
Tag @thyttan in an issue to https://gitbub.com/espruino/BangleApps/issues to report problems or suggestions.
|
||||
|
||||
## Creator
|
||||
|
||||
thyttan
|
||||
|
||||
## Acknowledgements
|
||||
|
||||
Inspired by the conversation between Gordon Williams and user156427 linked here: https://forum.espruino.com/conversations/392219/
|
||||
|
After Width: | Height: | Size: 1.3 KiB |