update submodules
|
|
@ -245,4 +245,5 @@ module.exports = {
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
ignorePatterns: findGeneratedJS(["apps/", "modules/"]),
|
ignorePatterns: findGeneratedJS(["apps/", "modules/"]),
|
||||||
|
reportUnusedDisableDirectives: true,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -405,7 +405,7 @@ in an iframe.
|
||||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<script src="../../lib/interface.js"></script>
|
<script src="../../core/lib/interface.js"></script>
|
||||||
<div id="t">Loading...</div>
|
<div id="t">Loading...</div>
|
||||||
<script>
|
<script>
|
||||||
function onInit() {
|
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",
|
"description": "A detailed description of my great app",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "",
|
"tags": "",
|
||||||
"supports" : ["BANGLEJS2"],
|
"supports" : ["BANGLEJS2"],
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"7chname.app.js","url":"app.js"},
|
{"name":"7chname.app.js","url":"app.js"},
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,4 @@
|
||||||
Trigger on 1.04g now, and record 10 samples before trigger
|
Trigger on 1.04g now, and record 10 samples before trigger
|
||||||
0.03: Bangle.js 2 compatibility
|
0.03: Bangle.js 2 compatibility
|
||||||
0.04: Minor code improvements
|
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 acc;
|
||||||
var HZ = 100;
|
var HZ = 100;
|
||||||
var SAMPLES = 5*HZ; // 5 seconds
|
var SAMPLES = 6 * HZ; // 6 seconds
|
||||||
var SCALE = 5000;
|
var SCALE = 2000;
|
||||||
var THRESH = 1.04;
|
var THRESH = 1.4;
|
||||||
var accelx = new Int16Array(SAMPLES);
|
var accelx = new Int16Array(SAMPLES);
|
||||||
var accely = new Int16Array(SAMPLES); // North
|
var accely = new Int16Array(SAMPLES); // North
|
||||||
var accelz = new Int16Array(SAMPLES); // Into clock face
|
var accelz = new Int16Array(SAMPLES); // Into clock face
|
||||||
|
var timestep = new Int16Array(SAMPLES); // Into clock face
|
||||||
var accelIdx = 0;
|
var accelIdx = 0;
|
||||||
var lastAccel;
|
var lastAccel;
|
||||||
function accelHandlerTrigger(a) {"ram"
|
var timestep_start = 0;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
function accelHandlerTrigger(a) {
|
||||||
function recordStop() {"ram"
|
"ram"
|
||||||
//console.log("Length:",getTime()-tStart);
|
if (a.mag * 2 > THRESH) { // *2 because 8g mode
|
||||||
Bangle.setPollInterval(80); // default poll interval
|
timestep_start = getTime();
|
||||||
Bangle.accelWr(0x18,0b01101100); // off, +-4g
|
g.drawString("Recording", g.getWidth() / 2, g.getHeight() / 2, 1);
|
||||||
Bangle.accelWr(0x1B,0x0); // default 12.5hz output
|
Bangle.removeListener('accel', accelHandlerTrigger);
|
||||||
Bangle.accelWr(0x18,0b11101100); // +-4g
|
Bangle.on('accel', accelHandlerRecord);
|
||||||
Bangle.removeListener('accel',accelHandlerRecord);
|
lastAccel.forEach(accelHandlerRecord);
|
||||||
E.showMessage("Finished");
|
accelHandlerRecord(a);
|
||||||
showData();
|
} else {
|
||||||
}
|
if (lastAccel.length > 10) lastAccel.shift();
|
||||||
|
lastAccel.push(a);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
if (a>maxAccel) maxAccel=a;
|
}
|
||||||
vel += a/HZ;
|
|
||||||
if (vel>maxVel) maxVel=vel;
|
function accelHandlerRecord(a) {
|
||||||
}
|
"ram"
|
||||||
g.reset();
|
var i = accelIdx++;
|
||||||
g.setFont("6x8").setFontAlign(1,0);
|
accelx[i] = a.x * SCALE * 2; // *2 because of 8g mode
|
||||||
g.drawString("Max Y Accel: "+maxAccel.toFixed(2)+" g",g.getWidth()-14,g.getHeight()-50);
|
accely[i] = -a.y * SCALE * 2;
|
||||||
g.drawString("Max Y Vel: "+maxVel.toFixed(2)+" m/s",g.getWidth()-14,g.getHeight()-40);
|
accelz[i] = a.z * SCALE * 2;
|
||||||
g.drawString("Time moving: "+(tEnd-tStart)/HZ+" s",g.getWidth()-14,g.getHeight()-30);
|
timestep[i] = (getTime() - timestep_start) * 1000;
|
||||||
//console.log("End Velocity "+vel);
|
if (accelIdx >= SAMPLES) recordStop();
|
||||||
g.setFont("6x8").setFontAlign(0,0,1);
|
}
|
||||||
g.drawString("FINISH",g.getWidth()-4,g.getHeight()/2);
|
|
||||||
setWatch(function() {
|
function recordStart() {
|
||||||
showMenu();
|
"ram"
|
||||||
}, global.BTN2?BTN2:BTN);
|
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) {
|
function showBig(txt) {
|
||||||
g.clear(1);
|
g.clear(1);
|
||||||
g.setFontVector(80).setFontAlign(0,0);
|
g.setFontVector(80).setFontAlign(0, 0);
|
||||||
g.drawString(txt,g.getWidth()/2, g.getHeight()/2);
|
g.drawString(txt, g.getWidth() / 2, g.getHeight() / 2);
|
||||||
g.flip();
|
g.flip();
|
||||||
}
|
}
|
||||||
|
|
||||||
function countDown() {
|
function countDown() {
|
||||||
showBig(3);
|
showBig(3);
|
||||||
setTimeout(function() {
|
|
||||||
showBig(2);
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
showBig(1);
|
showBig(2);
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
recordStart();
|
showBig(1);
|
||||||
}, 800);
|
setTimeout(function() {
|
||||||
|
recordStart();
|
||||||
|
}, 800);
|
||||||
|
}, 1000);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}, 1000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMenu() {
|
function showMenu() {
|
||||||
Bangle.setLCDTimeout(10); // set timeout for LCD in menu
|
Bangle.setLCDTimeout(10); // set timeout for LCD in menu
|
||||||
var menu = {
|
var menu = {
|
||||||
"" : { title : "Acceleration Rec" },
|
"": { title: "Acceleration Rec" },
|
||||||
"Start" : function() {
|
"Start": function() {
|
||||||
E.showMenu();
|
E.showMenu();
|
||||||
if (accelIdx==0) countDown();
|
if (accelIdx == 0) countDown();
|
||||||
else E.showPrompt("Overwrite Recording?").then(ok=>{
|
else E.showPrompt("Overwrite Recording?").then(ok => {
|
||||||
if (ok) countDown(); else showMenu();
|
if (ok) countDown();
|
||||||
});
|
else showMenu();
|
||||||
},
|
});
|
||||||
"Plot" : function() {
|
},
|
||||||
E.showMenu();
|
"Plot": function() {
|
||||||
if (accelIdx) showData();
|
E.showMenu();
|
||||||
else E.showAlert("No Data").then(()=>{
|
if (accelIdx) showData(false);
|
||||||
showMenu();
|
else E.showAlert("No Data").then(() => {
|
||||||
});
|
showMenu();
|
||||||
},
|
});
|
||||||
"Save" : function() {
|
},
|
||||||
E.showMenu();
|
"Storage": function() {
|
||||||
if (accelIdx) showSaveMenu();
|
E.showMenu();
|
||||||
else E.showAlert("No Data").then(()=>{
|
if (require("Storage").list(/^acc.*\.csv$/).length)
|
||||||
showMenu();
|
StorageMenu();
|
||||||
});
|
else
|
||||||
},
|
E.showAlert("No Data").then(() => {
|
||||||
"Exit" : function() {
|
showMenu();
|
||||||
load();
|
});
|
||||||
},
|
},
|
||||||
};
|
"Exit": function() {
|
||||||
E.showMenu(menu);
|
load();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
E.showMenu(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSaveMenu() {
|
function showSaveMenu() {
|
||||||
var menu = {
|
E.showPrompt("Save recording?").then(ok => {
|
||||||
"" : { title : "Save" }
|
if (ok)
|
||||||
};
|
SaveFile();
|
||||||
[1,2,3,4,5,6].forEach(i=>{
|
else
|
||||||
var fn = "accelrec."+i+".csv";
|
showMenu();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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>`;
|
</div>`;
|
||||||
promise = promise.then(function() {
|
promise = promise.then(function() {
|
||||||
document.querySelector(`.btn[fn='${fn}'][act='save']`).addEventListener("click", 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() {
|
document.querySelector(`.btn[fn='${fn}'][act='delete']`).addEventListener("click", function() {
|
||||||
Util.showModal("Deleting...");
|
Util.showModal("Deleting...");
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "accelrec",
|
"id": "accelrec",
|
||||||
"name": "Acceleration Recorder",
|
"name": "Acceleration Recorder",
|
||||||
"shortName": "Accel Rec",
|
"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.",
|
"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",
|
"icon": "app.png",
|
||||||
"tags": "",
|
"tags": "",
|
||||||
|
|
|
||||||
|
|
@ -50,3 +50,4 @@
|
||||||
0.45: Fix new alarm when selectedAlarm is undefined
|
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.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.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.
|
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.
|
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));
|
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
|
||||||
|
|
||||||
function getLabel(e) {
|
function getLabel(e) {
|
||||||
const dateStr = e.date && require("locale").date(new Date(e.date), 1);
|
const dateStr = getDateText(e.date);
|
||||||
return (e.timer
|
return (e.timer
|
||||||
? require("time_utils").formatDuration(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)}` : ""))
|
: (dateStr ? `${dateStr}${e.rp?"*":""} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeRepeat(e)}` : ""))
|
||||||
) + (e.msg ? ` ${e.msg}` : "");
|
) + (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) {
|
function trimLabel(label, maxLength) {
|
||||||
if(settings.showOverflow) return label;
|
if(settings.showOverflow) return label;
|
||||||
return (label.length > maxLength
|
return (label.length > maxLength
|
||||||
|
|
@ -75,10 +79,10 @@ function formatAlarmProperty(msg) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showMainMenu(scroll, group) {
|
function showMainMenu(scroll, group, scrollback) {
|
||||||
const menu = {
|
const menu = {
|
||||||
"": { "title": group || /*LANG*/"Alarms & Timers", scroll: scroll },
|
"": { "title": group || /*LANG*/"Alarms & Timers", scroll: scroll },
|
||||||
"< Back": () => group ? showMainMenu() : load(),
|
"< Back": () => group ? showMainMenu(scrollback) : load(),
|
||||||
/*LANG*/"New...": () => showNewMenu(group)
|
/*LANG*/"New...": () => showNewMenu(group)
|
||||||
};
|
};
|
||||||
const getGroups = settings.showGroup && !group;
|
const getGroups = settings.showGroup && !group;
|
||||||
|
|
@ -98,7 +102,7 @@ function showMainMenu(scroll, group) {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!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();
|
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 title = date ? (isNew ? /*LANG*/"New Event" : /*LANG*/"Edit Event") : (isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm");
|
||||||
var keyboard = "textinput";
|
var keyboard = "textinput";
|
||||||
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
|
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
|
||||||
|
var datetimeinput;
|
||||||
|
try {datetimeinput = require("datetimeinput");} catch(e) {datetimeinput = null;}
|
||||||
|
|
||||||
const menu = {
|
const menu = {
|
||||||
"": { "title": title },
|
"": { "title": title },
|
||||||
|
|
@ -145,41 +151,66 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
|
||||||
prepareAlarmForSave(alarm, alarmIndex, time, date);
|
prepareAlarmForSave(alarm, alarmIndex, time, date);
|
||||||
saveAndReload();
|
saveAndReload();
|
||||||
showMainMenu(scroll, group);
|
showMainMenu(scroll, group);
|
||||||
},
|
}
|
||||||
/*LANG*/"Hour": {
|
};
|
||||||
value: time.h,
|
|
||||||
format: v => ("0" + v).substr(-2),
|
if (alarm.date && datetimeinput) {
|
||||||
min: 0,
|
menu[`${getDateText(date.toLocalISOString().slice(0,10))} ${require("time_utils").formatTime(time)}`] = {
|
||||||
max: 23,
|
value: date,
|
||||||
wrap: true,
|
format: v => "",
|
||||||
onchange: v => time.h = v
|
onchange: v => {
|
||||||
},
|
setTimeout(() => {
|
||||||
/*LANG*/"Minute": {
|
var datetime = new Date(v.getTime());
|
||||||
value: time.m,
|
datetime.setHours(time.h, time.m);
|
||||||
format: v => ("0" + v).substr(-2),
|
datetimeinput.input({datetime}).then(result => {
|
||||||
min: 0,
|
time.h = result.getHours();
|
||||||
max: 59,
|
time.m = result.getMinutes();
|
||||||
wrap: true,
|
prepareAlarmForSave(alarm, alarmIndex, time, result, true);
|
||||||
onchange: v => time.m = v
|
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate, scroll, group);
|
||||||
},
|
});
|
||||||
/*LANG*/"Day": {
|
}, 100);
|
||||||
value: date ? date.getDate() : null,
|
}
|
||||||
min: 1,
|
};
|
||||||
max: 31,
|
} else {
|
||||||
wrap: true,
|
Object.assign(menu, {
|
||||||
onchange: v => date.setDate(v)
|
/*LANG*/"Hour": {
|
||||||
},
|
value: time.h,
|
||||||
/*LANG*/"Month": {
|
format: v => ("0" + v).substr(-2),
|
||||||
value: date ? date.getMonth() + 1 : null,
|
min: 0,
|
||||||
format: v => require("date_utils").month(v),
|
max: 23,
|
||||||
onchange: v => date.setMonth((v+11)%12)
|
wrap: true,
|
||||||
},
|
onchange: v => time.h = v
|
||||||
/*LANG*/"Year": {
|
},
|
||||||
value: date ? date.getFullYear() : null,
|
/*LANG*/"Minute": {
|
||||||
min: new Date().getFullYear(),
|
value: time.m,
|
||||||
max: 2100,
|
format: v => ("0" + v).substr(-2),
|
||||||
onchange: v => date.setFullYear(v)
|
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": {
|
/*LANG*/"Message": {
|
||||||
value: alarm.msg,
|
value: alarm.msg,
|
||||||
format: formatAlarmProperty,
|
format: formatAlarmProperty,
|
||||||
|
|
@ -241,7 +272,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
|
||||||
saveAndReload();
|
saveAndReload();
|
||||||
showMainMenu(scroll, group);
|
showMainMenu(scroll, group);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
|
||||||
if (!keyboard) delete menu[/*LANG*/"Message"];
|
if (!keyboard) delete menu[/*LANG*/"Message"];
|
||||||
if (!keyboard || !settings.showGroup) delete menu[/*LANG*/"Group"];
|
if (!keyboard || !settings.showGroup) delete menu[/*LANG*/"Group"];
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
"id": "alarm",
|
"id": "alarm",
|
||||||
"name": "Alarms & Timers",
|
"name": "Alarms & Timers",
|
||||||
"shortName": "Alarms",
|
"shortName": "Alarms",
|
||||||
"version": "0.47",
|
"version": "0.48",
|
||||||
"description": "Set alarms and timers on your Bangle",
|
"description": "Set alarms and timers on your Bangle",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "tool,alarm",
|
"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.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.59: Whitelist: Try to resolve peer addresses using NRF.resolveAddress() - for 2v19 or 2v18 cutting edge builds
|
||||||
0.60: Minor code improvements
|
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
|
// Apply any settings-specific stuff
|
||||||
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
|
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.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.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
|
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
|
||||||
// ================================================== FIXING OLDER FIRMWARES
|
// ================================================== FIXING OLDER FIRMWARES
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "boot",
|
"id": "boot",
|
||||||
"name": "Bootloader",
|
"name": "Bootloader",
|
||||||
"version": "0.61",
|
"version": "0.63",
|
||||||
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
|
||||||
"icon": "bootloader.png",
|
"icon": "bootloader.png",
|
||||||
"type": "bootloader",
|
"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.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.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.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 storage = require("Storage");
|
||||||
let locale = require("locale");
|
let locale = require("locale");
|
||||||
let widgets = require("widget_utils");
|
let widgets = require("widget_utils");
|
||||||
let date = new Date();
|
let date = new Date();
|
||||||
let bgImage;
|
|
||||||
let configNumber = (storage.readJSON("boxclk.json", 1) || {}).selectedConfig || 0;
|
let configNumber = (storage.readJSON("boxclk.json", 1) || {}).selectedConfig || 0;
|
||||||
let fileName = 'boxclk' + (configNumber > 0 ? `-${configNumber}` : '') + '.json';
|
let fileName = 'boxclk' + (configNumber > 0 ? `-${configNumber}` : '') + '.json';
|
||||||
// Add a condition to check if the file exists, if it does not, default to 'boxclk.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);
|
let boxKeys = Object.keys(boxes);
|
||||||
|
|
||||||
boxKeys.forEach((key) => {
|
boxKeys.forEach((key) => {
|
||||||
|
|
@ -224,9 +216,7 @@
|
||||||
return function(boxes) {
|
return function(boxes) {
|
||||||
date = new Date();
|
date = new Date();
|
||||||
g.clear();
|
g.clear();
|
||||||
if (bgImage) {
|
background.fillRect(Bangle.appRect);
|
||||||
g.drawImage(bgImage, 0, 0);
|
|
||||||
}
|
|
||||||
if (boxes.time) {
|
if (boxes.time) {
|
||||||
boxes.time.string = modString(boxes.time, locale.time(date, isBool(boxes.time.short, true) ? 1 : 0));
|
boxes.time.string = modString(boxes.time, locale.time(date, isBool(boxes.time.short, true) ? 1 : 0));
|
||||||
updatePerMinute = isBool(boxes.time.short, true);
|
updatePerMinute = isBool(boxes.time.short, true);
|
||||||
|
|
@ -412,4 +402,4 @@
|
||||||
widgets.swipeOn();
|
widgets.swipeOn();
|
||||||
modSetColor();
|
modSetColor();
|
||||||
setup();
|
setup();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
"version": "0.05",
|
"version": "0.05",
|
||||||
"description": "A customizable clock with configurable text boxes that can be positioned to show your favorite background",
|
"description": "A customizable clock with configurable text boxes that can be positioned to show your favorite background",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
|
"dependencies" : { "clockbg":"module" },
|
||||||
"screenshots": [
|
"screenshots": [
|
||||||
{"url":"screenshot.png"},
|
{"url":"screenshot.png"},
|
||||||
{"url":"screenshot-1.png"},
|
{"url":"screenshot-1.png"},
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
0.01: New app!
|
0.01: New app!
|
||||||
0.02: Advertise accelerometer data and sensor location
|
0.02: Advertise accelerometer data and sensor location
|
||||||
0.03: Use the bleAdvert module
|
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 __assign = Object.assign;
|
||||||
var Layout_1 = require("Layout");
|
var Layout_1 = require("Layout");
|
||||||
|
|
@ -441,8 +440,6 @@ var _a;
|
||||||
NRF.setServices(ad, {
|
NRF.setServices(ad, {
|
||||||
uart: false,
|
uart: false,
|
||||||
});
|
});
|
||||||
var bangle2 = Bangle;
|
|
||||||
var cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : [];
|
|
||||||
for (var id in ad) {
|
for (var id in ad) {
|
||||||
var serv = ad[id];
|
var serv = ad[id];
|
||||||
var value = void 0;
|
var value = void 0;
|
||||||
|
|
@ -450,11 +447,7 @@ var _a;
|
||||||
value = serv[ch].value;
|
value = serv[ch].value;
|
||||||
break;
|
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
|
// @ts-expect-error helper
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
||||||
const __assign = Object.assign;
|
const __assign = Object.assign;
|
||||||
|
|
||||||
const Layout = require("Layout");
|
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){
|
for(const id in ad){
|
||||||
const serv = ad[id as BleServ];
|
const serv = ad[id as BleServ];
|
||||||
let value;
|
let value;
|
||||||
|
|
@ -783,16 +777,7 @@ enableSensors();
|
||||||
break;
|
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",
|
"id": "btadv",
|
||||||
"name": "btadv",
|
"name": "btadv",
|
||||||
"shortName": "btadv",
|
"shortName": "btadv",
|
||||||
"version": "0.03",
|
"version": "0.04",
|
||||||
"description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth",
|
"description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"tags": "health,tool,sensors,bluetooth",
|
"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)
|
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.04: Fix duplicate button on edit->save
|
||||||
0.05: Use the bleAdvert module
|
0.05: Use the bleAdvert module
|
||||||
|
0.06: button number can't be 0. Now generates number automatically
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "bthome",
|
{ "id": "bthome",
|
||||||
"name": "BTHome",
|
"name": "BTHome",
|
||||||
"shortName":"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",
|
"description": "Allow your Bangle to advertise with BTHome and send events to Home Assistant via Bluetooth",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"type": "app",
|
"type": "app",
|
||||||
|
|
|
||||||
|
|
@ -11,10 +11,14 @@
|
||||||
require("Storage").writeJSON("bthome.json",settings)
|
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) {
|
function showButtonMenu(button, isNew) {
|
||||||
var isNew = false;
|
|
||||||
if (!button) {
|
if (!button) {
|
||||||
button = {name:"home", icon:"home", n:0, v:"press"};
|
button = {name:"home", icon:"home", n:getNewIdNumber(), v:"press"};
|
||||||
isNew = true;
|
isNew = true;
|
||||||
}
|
}
|
||||||
var actions = ["press","double_press","triple_press","long_press","long_double_press","long_triple_press"];
|
var actions = ["press","double_press","triple_press","long_press","long_double_press","long_triple_press"];
|
||||||
|
|
@ -51,10 +55,6 @@
|
||||||
format : v => actions[v],
|
format : v => actions[v],
|
||||||
onchange : v => button.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" : () => {
|
/*LANG*/"Save" : () => {
|
||||||
if (isNew) settings.buttons.push(button);
|
if (isNew) settings.buttons.push(button);
|
||||||
saveSettings();
|
saveSettings();
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
0.01: New App!
|
0.01: New App!
|
||||||
0.02: Handle the case where other apps have set bleAdvert to an array
|
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
|
pressure100&255,(pressure100>>8)&255,pressure100>>16
|
||||||
];
|
];
|
||||||
|
|
||||||
if(Array.isArray(Bangle.bleAdvert)){
|
require("ble_advert").set(0xFCD2, advert);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the temperature in the most accurate way with pressure sensor
|
// 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);});
|
Bangle.getPressure().then(p =>{if (p) onTemperature(p);});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
|
|
||||||
setInterval(function() {
|
setInterval(function() {
|
||||||
drawTemperature();
|
drawTemperature();
|
||||||
}, 10000); // update every 10s
|
}, 10000); // update every 10s
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{ "id": "bthometemp",
|
{ "id": "bthometemp",
|
||||||
"name": "BTHome Temperature and Pressure",
|
"name": "BTHome Temperature and Pressure",
|
||||||
"shortName":"BTHome T",
|
"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",
|
"description": "Displays temperature and pressure, and advertises them over bluetooth for Home Assistant using BTHome.io standard",
|
||||||
"icon": "app.png",
|
"icon": "app.png",
|
||||||
"tags": "bthome,bluetooth,temperature",
|
"tags": "bthome,bluetooth,temperature",
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,4 @@
|
||||||
0.02: Hiding widgets while showing the code
|
0.02: Hiding widgets while showing the code
|
||||||
0.03: Added option to use max brightness when showing code
|
0.03: Added option to use max brightness when showing code
|
||||||
0.04: Minor code improvements
|
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) {
|
function printLinearCode(binary) {
|
||||||
|
var padding = 5;
|
||||||
var yFrom = 15;
|
var yFrom = 15;
|
||||||
var yTo = 28;
|
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++){
|
for(var b = 0; b < binary.length; b++){
|
||||||
var x = b * width;
|
var x = b * width;
|
||||||
if(binary[b] === "1"){
|
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]){
|
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);
|
printLinearCode(code.encode().data);
|
||||||
break;
|
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:
|
default:
|
||||||
g.clear(true);
|
g.clear(true);
|
||||||
g.setFont("Vector:30");
|
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",
|
"id": "cards",
|
||||||
"name": "Cards",
|
"name": "Cards",
|
||||||
"version": "0.04",
|
"version": "0.05",
|
||||||
"description": "Display loyalty cards",
|
"description": "Display loyalty cards",
|
||||||
"icon": "app.png",
|
"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"}],
|
"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",
|
"tags": "cards",
|
||||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||||
|
"allow_emulator": true,
|
||||||
"readme": "README.md",
|
"readme": "README.md",
|
||||||
"storage": [
|
"storage": [
|
||||||
{"name":"cards.app.js","url":"app.js"},
|
{"name":"cards.app.js","url":"app.js"},
|
||||||
|
|
@ -15,6 +16,12 @@
|
||||||
{"name":"cards.qrcode.js","url":"qrcode.js"},
|
{"name":"cards.qrcode.js","url":"qrcode.js"},
|
||||||
{"name":"cards.codabar.js","url":"codabar.js"},
|
{"name":"cards.codabar.js","url":"codabar.js"},
|
||||||
{"name":"cards.code39.js","url":"code39.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}
|
{"name":"cards.img","url":"app-icon.js","evaluate":true}
|
||||||
],
|
],
|
||||||
"data": [{"name":"cards.settings.json"}]
|
"data": [{"name":"cards.settings.json"}]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* A module of Geo functions for use with gps fixes
|
* A module of Geo functions for use with gps fixes
|
||||||
*
|
*
|
||||||
* let geo = require("geotools");
|
* let geo = require("geotools");
|
||||||
|
|
@ -71,7 +71,7 @@ OsGridRef.latLongToOsGrid = function(point) {
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
function to_map_ref(digits, easting, northing) {
|
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 e = easting;
|
||||||
let n = northing;
|
let n = northing;
|
||||||
|
|
@ -108,7 +108,7 @@ function to_map_ref(digits, easting, northing) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* Module exports section, example code below
|
* Module exports section, example code below
|
||||||
*
|
*
|
||||||
* let geo = require("geotools");
|
* let geo = require("geotools");
|
||||||
|
|
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
0.01: New App!
|
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.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);
|
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 {
|
return {
|
||||||
text : s.toString().padStart(2,0)+"s",
|
text : s.toString().padStart(2,0),
|
||||||
img : g.asImage("string")
|
img : g.asImage("string")
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "clkinfosec",
|
{ "id": "clkinfosec",
|
||||||
"name": "Secondx Clockinfo",
|
"name": "Seconds Clockinfo",
|
||||||
"version":"0.01",
|
"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)",
|
"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",
|
"icon": "app.png",
|
||||||
"screenshots": [{"url":"screenshot.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.09: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
|
||||||
0.10: Use widget_utils.
|
0.10: Use widget_utils.
|
||||||
0.11: Minor code improvements
|
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)
|
* 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
|
* 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 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%
|
* 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.
|
* 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)
|
[MyLocation](https://banglejs.com/apps/?id=mylocation)
|
||||||
* The screen is updated every minute to save battery power
|
* 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
|
* 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
|
## Future Development
|
||||||
* Use mini icons in the information line rather that text
|
* Use mini icons in the information line rather that text
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,7 @@ function loadSettings() {
|
||||||
settings.gy = settings.gy||'#020';
|
settings.gy = settings.gy||'#020';
|
||||||
settings.fg = settings.fg||'#0f0';
|
settings.fg = settings.fg||'#0f0';
|
||||||
settings.idle_check = (settings.idle_check === undefined ? true : settings.idle_check);
|
settings.idle_check = (settings.idle_check === undefined ? true : settings.idle_check);
|
||||||
|
settings.batt_hours = (settings.batt_hours === undefined ? false : settings.batt_hours);
|
||||||
assignPalettes();
|
assignPalettes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,13 +113,39 @@ function updateSunRiseSunSet(now, lat, lon, line){
|
||||||
sunSet = extractTime(times.sunset);
|
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 = {
|
const infoData = {
|
||||||
ID_DATE: { calc: () => {var d = (new Date()).toString().split(" "); return d[2] + ' ' + d[1] + ' ' + d[3];} },
|
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_DAY: { calc: () => {var d = require("locale").dow(new Date()).toLowerCase(); return d[0].toUpperCase() + d.substring(1);} },
|
||||||
ID_SR: { calc: () => 'SUNRISE ' + sunRise },
|
ID_SR: { calc: () => 'SUNRISE ' + sunRise },
|
||||||
ID_SS: { calc: () => 'SUNSET ' + sunSet },
|
ID_SS: { calc: () => 'SUNSET ' + sunSet },
|
||||||
ID_STEP: { calc: () => 'STEPS ' + getSteps() },
|
ID_STEP: { calc: () => 'STEPS ' + getSteps() },
|
||||||
ID_BATT: { calc: () => 'BATTERY ' + E.getBattery() + '%' },
|
ID_BATT: { calc: batteryString},
|
||||||
ID_HRM: { calc: () => hrmCurrent }
|
ID_HRM: { calc: () => hrmCurrent }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{ "id": "daisy",
|
{ "id": "daisy",
|
||||||
"name": "Daisy",
|
"name": "Daisy",
|
||||||
"version": "0.11",
|
"version": "0.14",
|
||||||
"dependencies": {"mylocation":"app"},
|
"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",
|
"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",
|
"icon": "app.png",
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
let s = {'gy' : '#020',
|
let s = {'gy' : '#020',
|
||||||
'fg' : '#0f0',
|
'fg' : '#0f0',
|
||||||
'color': 'Green',
|
'color': 'Green',
|
||||||
'check_idle' : true};
|
'check_idle' : true,
|
||||||
|
'batt_hours' : false};
|
||||||
|
|
||||||
// ...and overwrite them with any saved values
|
// ...and overwrite them with any saved values
|
||||||
// This way saved values are preserved if a new version adds more settings
|
// This way saved values are preserved if a new version adds more settings
|
||||||
|
|
@ -45,6 +46,14 @@
|
||||||
s.idle_check = v;
|
s.idle_check = v;
|
||||||
save();
|
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 |