Merge branch 'espruino:master' into master
|
@ -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 }
|
||||
]
|
||||
}
|
|
@ -11,3 +11,4 @@
|
|||
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
|
||||
|
|
|
@ -116,7 +116,7 @@ function updateSunRiseSunSet(now, lat, lon, line){
|
|||
function batteryString(){
|
||||
let stringToInsert;
|
||||
if (settings.batt_hours) {
|
||||
var batt_usage = 200000/E.getPowerUsage().total;
|
||||
var batt_usage = require("power_usage").get().hrsLeft;
|
||||
let rounded;
|
||||
if (batt_usage > 24) {
|
||||
var days = Math.floor(batt_usage/24);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "daisy",
|
||||
"name": "Daisy",
|
||||
"version": "0.13",
|
||||
"version": "0.14",
|
||||
"dependencies": {"mylocation":"app"},
|
||||
"description": "A beautiful digital clock with large ring guage, idle timer and a cyclic information line that includes, day, date, steps, battery, sunrise and sunset times",
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -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}
|
||||
]
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New Clock Nifty A ++ >> adding more information on the right side of the clock
|
||||
0.02: Fix weather icon for languages other than English
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const w = require("weather");
|
||||
//const locale = require("locale");
|
||||
const locale = require("locale");
|
||||
|
||||
// Weather icons from https://icons8.com/icon/set/weather/color
|
||||
function getSun() {
|
||||
|
@ -67,6 +67,33 @@ function chooseIcon(condition) {
|
|||
return getPartSun;
|
||||
} else return getErr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Choose weather icon to display based on weather conditition code
|
||||
* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
|
||||
*/
|
||||
function chooseIconByCode(code) {
|
||||
const codeGroup = Math.round(code / 100);
|
||||
switch (codeGroup) {
|
||||
case 2: return getStorm;
|
||||
case 3: return getRain;
|
||||
case 5:
|
||||
switch (code) {
|
||||
case 511: return getSnow;
|
||||
default: return getRain;
|
||||
}
|
||||
case 6: return getSnow;
|
||||
case 7: return getPartSun;
|
||||
case 8:
|
||||
switch (code) {
|
||||
case 800: return getSun;
|
||||
case 804: return getCloud;
|
||||
default: return getPartSun;
|
||||
}
|
||||
default: return getCloud;
|
||||
}
|
||||
}
|
||||
|
||||
/*function condenseWeather(condition) {
|
||||
condition = condition.toLowerCase();
|
||||
if (condition.includes("thunderstorm") ||
|
||||
|
@ -143,8 +170,17 @@ const clock = new ClockFace({
|
|||
//let cWea =(curr === "no data" ? "no data" : curr.txt);
|
||||
let cTemp= (curr === "no data" ? 273 : curr.temp);
|
||||
// const temp = locale.temp(curr.temp - 273.15).match(/^(\D*\d*)(.*)$/);
|
||||
let w_icon = chooseIcon(curr.txt === undefined ? "no data" : curr.txt );
|
||||
//let w_icon = chooseIcon(curr.txt);
|
||||
|
||||
let w_icon = getErr;
|
||||
if (locale.name === "en" || locale.name === "en_GB" || locale.name === "en_US") {
|
||||
w_icon = chooseIcon(curr.txt === undefined ? "no data" : curr.txt);
|
||||
} else {
|
||||
// cannot use condition string to determine icon if language is not English; use weather code instead
|
||||
const code = curr.code || -1;
|
||||
if (code > 0) {
|
||||
w_icon = chooseIconByCode(curr.code);
|
||||
}
|
||||
}
|
||||
|
||||
g.setFontAlign(1, 0).setFont("Vector", 90 * this.scale);
|
||||
g.drawString(format(hour), this.centerTimeScaleX, this.center.y - 31 * this.scale);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "ffcniftyapp",
|
||||
"name": "Nifty-A Clock ++",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "A nifty clock with time and date and more",
|
||||
"dependencies": {"weather":"app"},
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
0.01: attempt to import
|
||||
0.02: implement colors and lines
|
|
@ -0,0 +1,20 @@
|
|||
# Icon Bits 
|
||||
|
||||
Bitmap editor suitable for creating icons and fonts for BangleJS2.
|
||||
|
||||
You'll want to run a copy of this in simulator, and another one on
|
||||
watch to view the results.
|
||||
|
||||
Draw using the provided tools, then press the button, and you'll get
|
||||
result on the console; you can also use "dump();" on command
|
||||
line. show_icon() takes same parameter as is used in app-icon.js
|
||||
files, you can just copy&paste it to get an icon. By using
|
||||
"for_screen();" command, then taking a screenshot, you can easily
|
||||
generate app.png file.
|
||||
|
||||
It is also possible to load existing icon into editor, using
|
||||
"load_icon("");" command. At the end of iconbits.app.js file there are
|
||||
more utility functions.
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
require("heatshrink").decompress(atob("mEwhHXAH4A/AH4A/ACkAgAuuGFovuL2Qu/F2amDABRdgC5QKDHSIvdOZQuaApYuGJBYvPJYwvLPBgvOJY4hGRYwvYJZAFGXQy/mdRAvqZpguSF5waMF8IaNF74aPF6ghTF/4UFAApfuGoyPwABoufSEQv/BpowRF6i6IF8oEDHAwueF/4vwD6gvZbyA/VCwIXHL8hQZZ8CQSIRhAiGIbhEcxQvaGo4YCAgQAJF7pBUIQgvnVY4vrAHQ="))
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
|
@ -0,0 +1,400 @@
|
|||
// Icon bits, thanks to tinydraw
|
||||
/* eslint-disable no-unused-vars */
|
||||
/* We have functions we expect user to call from command line, so they
|
||||
* appear "unused" to lint */
|
||||
|
||||
// font, draw, icon
|
||||
let mode;
|
||||
let pen = 'pixel';
|
||||
let discard = null;
|
||||
let kule = [0, 0, 0]; // R, G, B
|
||||
var font_height = 22, font_width = 8;
|
||||
var zoom_x = 64, zoom_y = 24, zoom_f = 6;
|
||||
var color = true;
|
||||
let oldLock = false;
|
||||
let sg = null;
|
||||
const top_bar = 20;
|
||||
|
||||
function clear(m) {
|
||||
sg.setColor(1,1,1).fillRect(0,0, font_width, font_height);
|
||||
}
|
||||
|
||||
function __setup(m) {
|
||||
mode = m;
|
||||
switch (m) {
|
||||
case 'font':
|
||||
font_height = 22;
|
||||
font_width = 8;
|
||||
zoom_x = 64;
|
||||
zoom_y = 24;
|
||||
zoom_f = 6;
|
||||
break;
|
||||
case 'draw':
|
||||
return;
|
||||
case 'icon':
|
||||
font_height = 48;
|
||||
font_width = 48;
|
||||
zoom_x = 56;
|
||||
zoom_y = 24;
|
||||
zoom_f = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
function setup(m) {
|
||||
__setup(m);
|
||||
sg = Graphics.createArrayBuffer(font_width, font_height, 8, {});
|
||||
clear();
|
||||
}
|
||||
|
||||
function icon_big() {
|
||||
zoom_x = 16;
|
||||
zoom_y = 25;
|
||||
zoom_f = 3;
|
||||
}
|
||||
|
||||
function icon_small() {
|
||||
__setup("icon");
|
||||
}
|
||||
|
||||
function updateLock() {
|
||||
if (oldLock) {
|
||||
return;
|
||||
}
|
||||
g.setColor('#fff');
|
||||
g.fillRect(0, 0, g.getWidth(), 20);
|
||||
g.setFont('Vector', 22);
|
||||
g.setColor('#000');
|
||||
g.drawString('PLEASE\nUNLOCK', 10, 2);
|
||||
oldLock = true;
|
||||
}
|
||||
Bangle.on("lock", function() {
|
||||
if (Bangle.isLocked()) {
|
||||
updateLock();
|
||||
} else {
|
||||
oldLock = false;
|
||||
drawUtil();
|
||||
}
|
||||
});
|
||||
function nextColor() {
|
||||
kule[0] = Math.random();
|
||||
kule[1] = Math.random();
|
||||
kule[2] = Math.random();
|
||||
}
|
||||
function selectColor(x) {
|
||||
if (color) {
|
||||
let i = Math.floor((x - 32) / 4);
|
||||
kule = toColor(i);
|
||||
return;
|
||||
}
|
||||
let c = 255;
|
||||
if (x < g.getWidth()/2) {
|
||||
c = 0;
|
||||
}
|
||||
kule[0] = c;
|
||||
kule[1] = c;
|
||||
kule[2] = c;
|
||||
}
|
||||
function nextPen () {
|
||||
switch (pen) {
|
||||
case 'circle': pen = 'pixel'; break;
|
||||
case 'pixel': pen = 'line'; break;
|
||||
case 'line': pen = 'square'; break;
|
||||
case 'square': pen = 'circle'; break;
|
||||
default: pen = 'pixel'; break;
|
||||
}
|
||||
drawUtil();
|
||||
|
||||
discard = setTimeout(function () { oldX = -1; oldY = -1; console.log('timeout'); discard = null; }, 500);
|
||||
}
|
||||
|
||||
var oldX = -1, oldY = -1;
|
||||
var line_from = null;
|
||||
|
||||
function drawBrushIcon () {
|
||||
const w = g.getWidth();
|
||||
switch (pen) {
|
||||
case 'circle':
|
||||
g.fillCircle(w - 10, 10, 5);
|
||||
break;
|
||||
case 'square':
|
||||
g.fillRect(w - 5, 5, w - 15, 15);
|
||||
break;
|
||||
case 'pixel':
|
||||
g.setPixel(10, 10);
|
||||
g.fillCircle(w - 10, 10, 2);
|
||||
break;
|
||||
case 'crayon':
|
||||
g.drawLine(w - 10, 5, w - 10, 15);
|
||||
g.drawLine(w - 14, 6, w - 10, 12);
|
||||
g.drawLine(w - 6, 6, w - 10, 12);
|
||||
break;
|
||||
case 'line':
|
||||
g.drawLine(w - 5, 5, w - 15, 15);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function drawArea() {
|
||||
g.clear();
|
||||
if (mode == "draw")
|
||||
return;
|
||||
const w = g.getWidth;
|
||||
g.setColor(0, 0, 0.5);
|
||||
g.fillRect(0, 0, g.getWidth(), g.getHeight());
|
||||
g.setColor(1, 1, 1);
|
||||
g.fillRect(zoom_x, zoom_y, zoom_x+8*zoom_f, zoom_y+font_height*zoom_f);
|
||||
g.setColor(1, 1, 0.75);
|
||||
for (let y=0; y<font_height; y++)
|
||||
g.drawLine(zoom_x, zoom_y+y*zoom_f, zoom_x+font_width*zoom_f, zoom_y+y*zoom_f);
|
||||
for (let x=0; x<font_width; x++)
|
||||
g.drawLine(zoom_x+x*zoom_f, zoom_y, zoom_x+x*zoom_f, zoom_y+font_height*zoom_f);
|
||||
update();
|
||||
}
|
||||
|
||||
function toColor(i) {
|
||||
let r = [0, 0, 0];
|
||||
r[0] = (i % 3) / 2;
|
||||
i = Math.floor(i / 3);
|
||||
r[1] = (i % 3) / 2;
|
||||
i = Math.floor(i / 3);
|
||||
r[2] = (i % 3) / 2;
|
||||
return r;
|
||||
}
|
||||
|
||||
function drawUtil() {
|
||||
if (Bangle.isLocked()) {
|
||||
updateLock();
|
||||
}
|
||||
// titlebar
|
||||
g.setColor(kule[0], kule[1], kule[2]);
|
||||
g.fillRect(0, 0, g.getWidth(), top_bar);
|
||||
for (let i = 0; i < 3*3*3; i++) {
|
||||
let r = toColor(i);
|
||||
g.setColor(r[0], r[1], r[2]);
|
||||
g.fillRect(32+4*i, 12, 32+4*i+3, top_bar);
|
||||
}
|
||||
// clear button
|
||||
g.setColor('#000'); // black
|
||||
g.fillCircle(10, 10, 8, 8);
|
||||
g.setColor('#fff');
|
||||
g.drawLine(8, 8, 13, 13);
|
||||
g.drawLine(13, 8, 8, 13);
|
||||
// tool button
|
||||
g.setColor('#fff');
|
||||
g.fillCircle(g.getWidth() - 10, 10, 8);
|
||||
g.setColor('#000');
|
||||
drawBrushIcon();
|
||||
}
|
||||
|
||||
function transform(p) {
|
||||
if (p.x < zoom_x || p.y < zoom_y)
|
||||
return p;
|
||||
p.x = ((p.x - zoom_x) / zoom_f);
|
||||
if (false)
|
||||
p.x = font_width - p.x;
|
||||
p.y = ((p.y - zoom_y) / zoom_f);
|
||||
return p;
|
||||
}
|
||||
|
||||
function __draw(g, from, to) {
|
||||
let XS = (to.x - from.x) / 32;
|
||||
let YS = (to.y - from.y) / 32;
|
||||
|
||||
switch (pen) {
|
||||
case 'line':
|
||||
case 'pixel':
|
||||
g.drawLine(from.x, from.y, to.x, to.y);
|
||||
break;
|
||||
case 'crayon':
|
||||
g.drawLine(from.x, from.y, to.x, to.y);
|
||||
g.drawLine(from.x + 1, from.y + 2, to.x, to.y - 2);
|
||||
g.drawLine(from.x + 2, from.y + 2, to.x, to.y + 2);
|
||||
break;
|
||||
case 'circle':
|
||||
for (let i = 0; i < 32; i++) {
|
||||
g.fillCircle(from.x + (i * XS), from.y + (i * YS), 2, 2);
|
||||
}
|
||||
break;
|
||||
case 'square':
|
||||
for (let i = 0; i < 32; i++) {
|
||||
const posX = from.x + (i * XS);
|
||||
const posY = from.y + (i * YS);
|
||||
g.fillRect(posX - 4, posY - 4, posX + 4, posY + 4);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
print("Unkown pen ", pen);
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
if (zoom_f < 3)
|
||||
g.drawImage(sg, 4, 64, {});
|
||||
g.drawImage(sg, zoom_x, zoom_y, { scale: zoom_f });
|
||||
}
|
||||
|
||||
function do_draw(from, to) {
|
||||
from = transform(from);
|
||||
to = transform(to);
|
||||
if (from && to) {
|
||||
__draw(sg, from, to);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
let tapTimer = null;
|
||||
let dragTimer = null;
|
||||
function on_drag (tap) {
|
||||
let from = { x: tap.x, y: tap.y };
|
||||
const to = { x: tap.x + tap.dx, y: tap.y + tap.dy };
|
||||
if (oldX != -1) {
|
||||
from = { x: oldX, y: oldY };
|
||||
}
|
||||
if (tap.b === 0) {
|
||||
if (tapTimer !== null) {
|
||||
clearTimeout(tapTimer);
|
||||
tapTimer = null;
|
||||
}
|
||||
}
|
||||
if (dragTimer != null) {
|
||||
clearTimeout(dragTimer);
|
||||
dragTimer = null;
|
||||
}
|
||||
dragTimer = setTimeout(function () {
|
||||
oldX = -1;
|
||||
oldY = -1;
|
||||
}, 100);
|
||||
|
||||
// tap and hold the clear button
|
||||
if (tap.x < 32 && tap.y < top_bar) {
|
||||
if (tap.b === 1) {
|
||||
if (tapTimer === null) {
|
||||
tapTimer = setTimeout(function () {
|
||||
clear();
|
||||
drawArea();
|
||||
drawUtil();
|
||||
tapTimer = null;
|
||||
}, 800);
|
||||
}
|
||||
if (discard) {
|
||||
clearTimeout(discard); discard = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (tap.x > g.getWidth() - 32 && tap.y < top_bar) {
|
||||
if (tap.b === 1) {
|
||||
if (tapTimer === null) {
|
||||
tapTimer = setTimeout(function () {
|
||||
g.clear();
|
||||
drawUtil();
|
||||
oldX = -1; oldY = -1;
|
||||
|
||||
tapTimer = null;
|
||||
}, 800);
|
||||
}
|
||||
if (discard) {
|
||||
clearTimeout(discard);
|
||||
discard = null;
|
||||
return;
|
||||
}
|
||||
nextPen();
|
||||
}
|
||||
drawUtil();
|
||||
return;
|
||||
} else if (tap.y < top_bar) {
|
||||
if (mode == "draw")
|
||||
nextColor();
|
||||
else
|
||||
selectColor(tap.x);
|
||||
drawUtil();
|
||||
return;
|
||||
}
|
||||
sg.setColor(kule[0], kule[1], kule[2]);
|
||||
g.setColor(kule[0], kule[1], kule[2]);
|
||||
oldX = to.x;
|
||||
oldY = to.y;
|
||||
|
||||
if (pen != "line") {
|
||||
do_draw(from, to);
|
||||
} else {
|
||||
if (tap.b == 1) {
|
||||
print(line_from);
|
||||
if (!line_from) {
|
||||
line_from = to;
|
||||
} else {
|
||||
do_draw(line_from, to);
|
||||
line_from = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
drawUtil();
|
||||
}
|
||||
|
||||
function dump(n) {
|
||||
function f(i) {
|
||||
return "\\x" + i.toString(16).padStart(2, '0');
|
||||
}
|
||||
let s = f(0) + f(font_width) + f(font_height) + f(1);
|
||||
// 0..black, 65535..white
|
||||
for (let y = 0; y < font_height; y++) {
|
||||
let v = 0;
|
||||
for (let x = 0; x < font_width; x++) {
|
||||
let p = sg.getPixel(x, y);
|
||||
v = v << 1 | (p==0);
|
||||
}
|
||||
s += f(v);
|
||||
}
|
||||
if (mode == "font")
|
||||
print('show_font("' + s + '");');
|
||||
var im = sg.asImage("string");
|
||||
//print('show_unc_icon("'+btoa(im)+'");');
|
||||
print('show_icon("'+btoa(require('heatshrink').compress(im))+'");');
|
||||
}
|
||||
|
||||
setup("icon");
|
||||
drawArea();
|
||||
Bangle.setUI({
|
||||
"mode": "custom",
|
||||
"drag": on_drag,
|
||||
"btn": dump,
|
||||
});
|
||||
drawUtil();
|
||||
|
||||
function show_font(icon) {
|
||||
g.reset().clear();
|
||||
g.setFont("Vector", 26).drawString("Hellord" + icon, 0, 0);
|
||||
}
|
||||
|
||||
function show_bin_icon(icon) {
|
||||
g.reset().clear();
|
||||
g.drawImage(icon, 40, 40);
|
||||
}
|
||||
|
||||
function show_unc_icon(icon) {
|
||||
show_bin_icon(atob(icon));
|
||||
}
|
||||
|
||||
function show_icon(icon) {
|
||||
let unc = require("heatshrink").decompress(atob(icon));
|
||||
show_bin_icon(unc);
|
||||
}
|
||||
|
||||
function load_bin_icon(i) {
|
||||
sg.reset().clear();
|
||||
sg.drawImage(i, 0, 0);
|
||||
drawArea();
|
||||
}
|
||||
|
||||
function load_icon(icon) {
|
||||
let unc = require("heatshrink").decompress(atob(icon));
|
||||
load_bin_icon(unc);
|
||||
}
|
||||
|
||||
function for_screen() {
|
||||
g.reset().clear();
|
||||
icon_big();
|
||||
update();
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{ "id": "iconbits",
|
||||
"name": "Icon bits",
|
||||
"version": "0.02",
|
||||
"description": "Bitmap editor suitable for creating icons",
|
||||
"icon": "app.png",
|
||||
"readme": "README.md",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"tags": "tools",
|
||||
"storage": [
|
||||
{"name":"iconbits.app.js","url":"iconbits.app.js"},
|
||||
{"name":"iconbits.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -196,12 +196,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/sixths/sixths.app.js": {
|
||||
"hash": "2a4676828bdf78df052df402de34e6f1abd1c847ebe0d193fc789cd6e9dd0e5c",
|
||||
"rules": [
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/scribble/app.js": {
|
||||
"hash": "6d13abd27bab8009a6bdabe1df2df394bc14aac20c68f67e8f8b085fa6b427cd",
|
||||
"rules": [
|
||||
|
@ -1249,12 +1243,6 @@ module.exports = {
|
|||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/accelrec/app.js": {
|
||||
"hash": "b5369a60afc8f360f0b33f71080eb3f5d09a1bf3703acfcf07cd80dd19f1997d",
|
||||
"rules": [
|
||||
"no-undef"
|
||||
]
|
||||
},
|
||||
"apps/BLEcontroller/app-joy.js": {
|
||||
"hash": "e4f34bb1bc11b52c3d7a1c537a140b0e23ccef82694dcd602cb517a8ba342898",
|
||||
"rules": [
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
0.1: Initial release
|
||||
0.2: Draw line for 3d effect, fix number alignment
|
||||
0.3: Fix day-end overflowing hour calculation
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
# Measure Time
|
||||
|
||||
Measure time in a fancy way. Inspired by a Watchface I had on my first Pebble Watch.
|
||||
Icon from [Flaticon created by Smartline]("https://www.flaticon.com/free-icons/scale")
|
||||
|
||||
Written by [prefectAtEarth](https://www.github.com/prefectAtEarth/)
|
||||
Watchface written by [prefectAtEarth](https://www.github.com/prefectAtEarth/)
|
||||
|
||||

|
||||

|
||||

|
||||
|
|
|
@ -1 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4n/AAIHB/fe8EHrvv333xVS221jnnlFC7//9NP997zXWjHGn+EGJsu9wAC0AHBgugq99C5d0kUq1WtoAHBgnaw8nC5d9mdwgEN7QHBxvQ5nhGwQXNiQHB19A41xC5dy3YXCwAHBwkqx3tI5d3AAV8L4UIDYRkBogADpTOQhWqAAZOLAAuoxAABfyYXXI4pKRO4oACqBHl0QXWAC8IF4QABwpHRkUilALHgutvwvMBY8NoEHKakCqtHR5gAH1FY7wUFcYS/LI5Fwd4r7IqXuJ4uUAYMK1QABKhEKIAQAC1kW7SnDAAUlPxnBiN9xEnu93vx6KAAeHyMdI5wAGox3OS5GAU4oAEoAXJhTXGfigAWhAvWX6QvcT5nog5HJF5QXLX5AAC0levwXId5cNoAvJhWqAAILHgVAhxHMQaZfFwoXQI5YALO5ZHPC6bXDAAmADqYARhBHXkUilC/oA="))
|
||||
require("heatshrink").decompress(atob("mEwgX/6AHCh////hAQIAB4ALCg4GBwF/BZH+gEfBYXBAYQLB/AbDBY3ggEBBZGAqFAGAQLE/0B6HgGAXB1WrBYP4g/QAQJHDgYLB/kf6H+BY//v/Q/+fEYwGBBYJHIBdJHBBcJTvR5anFBaKz/Wf6zhv4LBz4LG/kf6H+DgQjE/EH6ACBHY3+gPQ8EfI4+AqFAv4LH8EAgJfI/EAFwRHCAAIHB/0AFwQLGGAIuCOAILCh4GB8IKC"))
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
{
|
||||
require("Font7x11Numeric7Seg").add(Graphics);
|
||||
g.setFont("7x11Numeric7Seg");
|
||||
g.setFontAlign(0, 0);
|
||||
|
||||
const centerY = g.getHeight() / 2; //88
|
||||
const lineStart = 25;
|
||||
|
@ -27,10 +25,10 @@
|
|||
var steps = [0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
|
||||
var stepsReversed = steps.slice();
|
||||
stepsReversed.reverse();
|
||||
var polyLeftTop = [];
|
||||
var polyLeftBottom = [];
|
||||
var polyRightTop = [];
|
||||
var polyRightBottom = [];
|
||||
var polyLeftTop = [0, 0];
|
||||
var polyLeftBottom = [0, g.getHeight()];
|
||||
var polyRightTop = [g.getWidth() - 1, 0];
|
||||
var polyRightBottom = [g.getWidth() - 1, g.getHeight()];
|
||||
let xL = 0;
|
||||
let xR = g.getWidth() - 1;
|
||||
let yT = centerY - 13;
|
||||
|
@ -70,6 +68,29 @@
|
|||
g.fillPolyAA(polyRightBottom, true);
|
||||
};
|
||||
|
||||
let hourStringXOffset = function (hour) {
|
||||
if (hour == 1) {
|
||||
return lineEndFull - 5;
|
||||
}
|
||||
if (hour < 10 || hour >= 20) {
|
||||
return lineEndFull + 5;
|
||||
}
|
||||
return lineEndFull - 5;
|
||||
};
|
||||
|
||||
let drawHourString = function(hour, yLines) {
|
||||
var hourForDrawing = 0;
|
||||
if (hour < 0) {
|
||||
// a negative hour => (+ and - = -)
|
||||
hourForDrawing = 24 + hour;
|
||||
} else if (hour >= 24) {
|
||||
hourForDrawing = hour - 24;
|
||||
} else {
|
||||
hourForDrawing = hour;
|
||||
}
|
||||
g.drawString(hourForDrawing, hourStringXOffset(hourForDrawing), yLines, true);
|
||||
};
|
||||
|
||||
let drawTime = function () {
|
||||
g.clear();
|
||||
var d = new Date();
|
||||
|
@ -86,19 +107,19 @@
|
|||
|
||||
var lineEnd = lineEndDefault;
|
||||
g.setFont("7x11Numeric7Seg", 2);
|
||||
g.setFontAlign(0, 0);
|
||||
g.setFontAlign(-1, 0);
|
||||
|
||||
// gone
|
||||
do {
|
||||
switch (yTopLines - 88 + mins) {
|
||||
case -60:
|
||||
lineEnd = lineEndFull;
|
||||
g.drawString(d.getHours() - 1, lineEnd + 10, yTopLines, true);
|
||||
drawHourString(d.getHours() - 1, yTopLines);
|
||||
break;
|
||||
case 0:
|
||||
case 60:
|
||||
lineEnd = lineEndFull;
|
||||
g.drawString(d.getHours(), lineEnd + 10, yTopLines, true);
|
||||
drawHourString(d.getHours(), yTopLines);
|
||||
break;
|
||||
case 45:
|
||||
case -45:
|
||||
|
@ -128,11 +149,11 @@
|
|||
case 0:
|
||||
case 60:
|
||||
lineEnd = lineEndFull;
|
||||
g.drawString(d.getHours() + 1, lineEnd + 10, yBottomLines, true);
|
||||
drawHourString(d.getHours() + 1, yBottomLines);
|
||||
break;
|
||||
case 120:
|
||||
lineEnd = lineEndFull;
|
||||
g.drawString(d.getHours() + 2, lineEnd + 10, yBottomLines, true);
|
||||
drawHourString(d.getHours() + 2, yBottomLines);
|
||||
break;
|
||||
case 15:
|
||||
case 75:
|
||||
|
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5.0 KiB |
|
@ -1,10 +1,13 @@
|
|||
{
|
||||
"id": "measuretime",
|
||||
"name": "Measure Time",
|
||||
"version": "0.1",
|
||||
"version": "0.3",
|
||||
"description": "Measure Time in a fancy way.",
|
||||
"icon": "small_measuretime.png",
|
||||
"screenshots": [{ "url": "measuretime.png" }],
|
||||
"icon": "measuretime_icon.png",
|
||||
"screenshots": [
|
||||
{ "url": "screenshot_light.png" },
|
||||
{ "url": "screenshot_dark.png" }
|
||||
],
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
After Width: | Height: | Size: 896 B |
After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 5.3 KiB |
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4X/AwX48AFCqoAEC4oL/Bf4L/Bf4LTAH4A/ADGqAAIL/Bf4LD"))
|
|
@ -0,0 +1,142 @@
|
|||
{
|
||||
const minute_boxes = [
|
||||
{x:0.5, y:0},
|
||||
{x:0.5, y:0.5},
|
||||
{x:0, y:0.5},
|
||||
{x:0, y:0},
|
||||
];
|
||||
|
||||
const hour_boxes = [
|
||||
{x:0.5, y:0},
|
||||
{x:0.75, y:0},
|
||||
{x:0.75, y:0.25},
|
||||
{x:0.75, y:0.5},
|
||||
{x:0.75, y:0.75},
|
||||
{x:0.5, y:0.75},
|
||||
{x:0.25, y:0.75},
|
||||
{x:0, y:0.75},
|
||||
{x:0, y:0.5},
|
||||
{x:0, y:0.25},
|
||||
{x:0, y:0},
|
||||
{x:0.25, y:0},
|
||||
];
|
||||
|
||||
let drawTimeout;
|
||||
|
||||
// schedule a draw for the next 15 minute period
|
||||
let queueDraw = function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, (60000 * 15) - (Date.now() % (60000 * 15)));
|
||||
};
|
||||
|
||||
// Main draw function
|
||||
let draw = function draw() {
|
||||
var d = new Date();
|
||||
var h = d.getHours(), m = d.getMinutes();
|
||||
|
||||
g.setBgColor(settings.backgroundColour);
|
||||
g.clearRect(Bangle.appRect);
|
||||
|
||||
if (settings.showBattery) {
|
||||
drawBattery();
|
||||
}
|
||||
|
||||
// Draw minute box
|
||||
drawBox(Math.floor(m/15), minute_boxes, Bangle.appRect.h/2, settings.minuteColour);
|
||||
|
||||
// Draw an hour box or write the number
|
||||
if (settings.digital) {
|
||||
g.setColor(settings.hourColour);
|
||||
g.setFont("Vector:60");
|
||||
g.setFontAlign(0,0);
|
||||
g.drawString(h, Bangle.appRect.x + Bangle.appRect.w/2, Bangle.appRect.y + Bangle.appRect.h/2);
|
||||
} else {
|
||||
drawBox(h % 12, hour_boxes, Bangle.appRect.h/4, settings.hourColour);
|
||||
}
|
||||
|
||||
queueDraw();
|
||||
};
|
||||
|
||||
// Draw battery box
|
||||
let drawBattery = function drawBattery() {
|
||||
// Round battery up to 10% interval
|
||||
let battery = Math.min((Math.floor(E.getBattery()/10)+1)/10, 1);
|
||||
|
||||
// Maximum battery box
|
||||
let batterySize = 30;
|
||||
|
||||
// Draw outer box at full brightness
|
||||
g.setColor(settings.batteryColour);
|
||||
g.drawRect(
|
||||
(Bangle.appRect.w / 2) - batterySize,
|
||||
(Bangle.appRect.h / 2) - batterySize + Bangle.appRect.y,
|
||||
(Bangle.appRect.w / 2) + batterySize,
|
||||
(Bangle.appRect.h / 2) + batterySize + Bangle.appRect.y
|
||||
);
|
||||
|
||||
// Fade battery colour and draw inner box
|
||||
g.setColor(settings.batteryColour.split('').map((c) => {
|
||||
return c=='f' ? Math.ceil(15 * battery).toString(16) : c;
|
||||
}).join(''));
|
||||
g.fillRect(
|
||||
(Bangle.appRect.w / 2) - (batterySize * battery),
|
||||
(Bangle.appRect.h / 2) - (batterySize * battery) + Bangle.appRect.y,
|
||||
(Bangle.appRect.w / 2) + (batterySize * battery),
|
||||
(Bangle.appRect.h / 2) + (batterySize * battery) + Bangle.appRect.y
|
||||
);
|
||||
};
|
||||
|
||||
// Draw hour or minute boxes
|
||||
let drawBox = function drawBox(current, boxes, size, colour) {
|
||||
let x1 = (boxes[current].x * Bangle.appRect.h) + (Bangle.appRect.y/2);
|
||||
let y1 = (boxes[current].y * Bangle.appRect.h) + Bangle.appRect.y;
|
||||
let x2 = x1 + size;
|
||||
let y2 = y1 + size;
|
||||
g.setColor(colour);
|
||||
g.fillRect(x1, y1, x2, y2);
|
||||
};
|
||||
|
||||
let settings = Object.assign({
|
||||
// Default values
|
||||
minuteColour: '#f00',
|
||||
hourColour: '#ff0',
|
||||
backgroundColour: 'theme',
|
||||
showWidgets: true,
|
||||
showBattery: true,
|
||||
digital: false,
|
||||
batteryColour: '#0f0'
|
||||
}, require('Storage').readJSON('quarterclock.json', true) || {});
|
||||
|
||||
if (settings.backgroundColour == 'theme') {
|
||||
settings.backgroundColour = g.theme.bg;
|
||||
}
|
||||
|
||||
// Set minuteColour to a darker shade if same as hourColour
|
||||
if (settings.minuteColour == settings.hourColour) {
|
||||
settings.minuteColour = settings.minuteColour.split('').map((c) => {
|
||||
return c=='f' ? '7' : c;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// Show launcher when middle button pressed
|
||||
// Remove handler to allow fast loading
|
||||
Bangle.setUI({mode:"clock", remove:function() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
require("widget_utils").show();
|
||||
}});
|
||||
|
||||
// Load and display widgets
|
||||
Bangle.loadWidgets();
|
||||
if (settings.showWidgets) {
|
||||
require("widget_utils").show();
|
||||
} else {
|
||||
require("widget_utils").hide();
|
||||
}
|
||||
|
||||
// draw initial boxes and queue subsequent redraws
|
||||
draw();
|
||||
}
|
||||
|
After Width: | Height: | Size: 252 B |
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"id": "quarterclock",
|
||||
"name": "Quarter Clock",
|
||||
"shortName":"Quarter Clock",
|
||||
"icon": "app.png",
|
||||
"screenshots" : [ { "url":"screenshot.png" } ],
|
||||
"version":"0.01",
|
||||
"description": "For those lazy days when the exact time doesn't matter. Small square shows the hour, large square shows the fifteen minute period, and centre square shows the battery level.",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"storage": [
|
||||
{"name":"quarterclock.app.js","url":"app.js"},
|
||||
{"name":"quarterclock.settings.js","url":"settings.js"},
|
||||
{"name":"quarterclock.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"data": [
|
||||
{"name":"quarterclock.json"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -0,0 +1,66 @@
|
|||
(function(back) {
|
||||
var FILE = 'quarterclock.json';
|
||||
// Load settings
|
||||
var settings = Object.assign({
|
||||
minuteColour: '#f00',
|
||||
hourColour: '#ff0',
|
||||
backgroundColour: 'theme',
|
||||
showWidgets: true,
|
||||
showBattery: true,
|
||||
digital: false,
|
||||
batteryColour: '#0f0',
|
||||
}, require('Storage').readJSON(FILE, true) || {});
|
||||
|
||||
function setSetting(key,value) {
|
||||
settings[key] = value;
|
||||
require('Storage').writeJSON(FILE, settings);
|
||||
}
|
||||
|
||||
// Helper method which uses int-based menu item for set of string values and their labels
|
||||
function stringItems(key, startvalue, values, labels) {
|
||||
return {
|
||||
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
|
||||
format: v => labels[v],
|
||||
min: 0,
|
||||
max: values.length - 1,
|
||||
wrap: true,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
setSetting(key,values[v]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Helper method which breaks string set settings down to local settings object
|
||||
function stringInSettings(name, values, labels) {
|
||||
return stringItems(name,settings[name], values, labels);
|
||||
}
|
||||
|
||||
// Show the menu
|
||||
E.showMenu({
|
||||
'' : { 'title' : 'Quarter Clock' },
|
||||
'< Back' : () => back(),
|
||||
'Hour Colour': stringInSettings('hourColour', ['#f00', '#0f0', '#00f', '#ff0', '#0ff', '#f0f'], ['Red', 'Green', 'Blue', 'Yellow', 'Cyan', 'Magenta']),
|
||||
'Minute Colour': stringInSettings('minuteColour', ['#f00', '#0f0', '#00f', '#ff0', '#0ff', '#f0f'], ['Red', 'Green', 'Blue', 'Yellow', 'Cyan', 'Magenta']),
|
||||
'Background Colour': stringInSettings('backgroundColour', ['theme', '#000', '#fff'],['theme', 'Black', 'White']),
|
||||
'Digital': {
|
||||
value: !!settings.digital, // !! converts undefined to false
|
||||
onchange: v => {
|
||||
setSetting('digital', v);
|
||||
},
|
||||
},
|
||||
'Show Widgets': {
|
||||
value: !!settings.showWidgets,
|
||||
onchange: v => {
|
||||
setSetting('showWidgets', v);
|
||||
},
|
||||
},
|
||||
'Show Battery': {
|
||||
value: !!settings.showBattery,
|
||||
onchange: v => {
|
||||
setSetting('showBattery', v);
|
||||
},
|
||||
},
|
||||
'Battery Colour': stringInSettings('batteryColour', ['#f00', '#0f0', '#00f', '#ff0', '#0ff', '#f0f'], ['Red', 'Green', 'Blue', 'Yellow', 'Cyan', 'Magenta']),
|
||||
});
|
||||
})
|
|
@ -651,11 +651,11 @@ function showUtilMenu() {
|
|||
E.showMessage(/*LANG*/'Flattening battery - this can take hours.\nLong-press button to cancel.');
|
||||
Bangle.setLCDTimeout(0);
|
||||
Bangle.setLCDPower(1);
|
||||
Bangle.setLCDBrightness(1);
|
||||
if (Bangle.setGPSPower) Bangle.setGPSPower(1,"flat");
|
||||
if (Bangle.setHRMPower) Bangle.setHRMPower(1,"flat");
|
||||
if (Bangle.setCompassPower) Bangle.setCompassPower(1,"flat");
|
||||
if (Bangle.setBarometerPower) Bangle.setBarometerPower(1,"flat");
|
||||
if (Bangle.setHRMPower) Bangle.setGPSPower(1,"flat");
|
||||
setInterval(function() {
|
||||
var i=1000;while (i--);
|
||||
}, 1);
|
||||
|
|
|
@ -2,3 +2,4 @@
|
|||
0.02: better GPS support, adding altitude and temperature support
|
||||
0.03: minor code improvements
|
||||
0.04: make height auto-calibration useful and slow ticks to save power
|
||||
0.05: add ability to navigate to waypoints, better documentation
|
||||
|
|
|
@ -24,17 +24,35 @@ minutes, real distance will be usually higher than approximation.
|
|||
|
||||
Useful gestures:
|
||||
|
||||
F -- disable GPS.
|
||||
G -- enable GPS for 4 hours in low power mode.
|
||||
N -- take a note and write it to the log.
|
||||
S -- enable GPS for 30 minutes in high power mode.
|
||||
B -- "Battery", show/buzz battery info
|
||||
D -- "Down", previous waypoint
|
||||
F -- "oFf", disable GPS.
|
||||
G -- "Gps", enable GPS for 4 hours in low power mode.
|
||||
I -- "Info", toggle info display
|
||||
L -- "aLtimeter", load altimeter app
|
||||
M -- "Mark", create mark from current position
|
||||
N -- "Note", take a note and write it to the log.
|
||||
O -- "Orloj", run orloj app
|
||||
R -- "Run", run "runplus" app
|
||||
S -- "Speed", enable GPS for 30 minutes in high power mode.
|
||||
T -- "Time", buzz current time
|
||||
U -- "Up", next waypoint
|
||||
Y -- "compass", reset compass
|
||||
|
||||
When application detects watch is being worn, it will use vibrations
|
||||
to communicate back to the user.
|
||||
|
||||
B -- battery low.
|
||||
E -- acknowledge, gesture understood.
|
||||
T -- start of new hour.
|
||||
|
||||
Three colored dots may appear on display. North is on the 12 o'clock
|
||||
position (top of the display).
|
||||
|
||||
red: this is direction to the waypoint.
|
||||
green: this is direction you are moving into, according to GPS.
|
||||
blue: this is direction top of watch faces, according to the compass.
|
||||
|
||||
Written by: [Pavel Machek](https://github.com/pavelmachek)
|
||||
|
||||
## Future Development
|
||||
|
@ -55,4 +73,27 @@ Todo:
|
|||
|
||||
*) only turn on compass when needed
|
||||
|
||||
*) adjust draw timeouts to save power
|
||||
*) only warn about battery low when it crosses thresholds, update
|
||||
battery low message
|
||||
|
||||
*) rename "show" to something else -- it collides with built-in
|
||||
|
||||
*) adjust clock according to GPS
|
||||
|
||||
*) show something more reasonable than (NOTEHERE).
|
||||
|
||||
*) hide messages after timeout.
|
||||
|
||||
*) show route lengths after the fact
|
||||
|
||||
*) implement longer recording than "G".
|
||||
|
||||
*) Probably T should be G.
|
||||
|
||||
*) sum gps distances for a day
|
||||
|
||||
*) allow setting up home altitude, or at least disable auto-calibration
|
||||
|
||||
*) show time-to-sunset / sunrise?
|
||||
|
||||
*) one-second updates when gps is active
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "sixths",
|
||||
"name": "Sixth sense",
|
||||
"version": "0.04",
|
||||
"version": "0.05",
|
||||
"description": "Clock for outdoor use with GPS support",
|
||||
"icon": "app.png",
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -3,18 +3,16 @@
|
|||
|
||||
// Options you'll want to edit
|
||||
const rest_altitude = 354;
|
||||
const geoid_to_sea_level = 0; // Maybe BangleJS2 already compensates?
|
||||
|
||||
const W = g.getWidth();
|
||||
const H = g.getHeight();
|
||||
|
||||
var cx = 100, cy = 105, sc = 70,
|
||||
temp = 0, alt = 0, bpm = 0;
|
||||
var cx = 100, cy = 105, sc = 70, temp = 0, alt = 0, bpm = 0;
|
||||
var buzz = "", /* Set this to transmit morse via vibrations */
|
||||
inm = "", l = "", /* For incoming morse handling */
|
||||
in_str = "",
|
||||
note = "(NOTEHERE)",
|
||||
debug = "v1119", debug2 = "(otherdb)", debug3 = "(short)";
|
||||
note = "",
|
||||
debug = "v0.04.1", debug2 = "(otherdb)", debug3 = "(short)";
|
||||
var mode = 0, mode_time = 0; // 0 .. normal, 1 .. note, 2.. mark name
|
||||
var disp_mode = 0; // 0 .. normal, 1 .. small time
|
||||
|
||||
|
@ -45,15 +43,15 @@ var cur_mark = null;
|
|||
// Icons
|
||||
|
||||
var 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";
|
||||
var 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";
|
||||
//var 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";
|
||||
var 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";
|
||||
var 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";
|
||||
var 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";
|
||||
|
||||
function toMorse(x) {
|
||||
var r = "";
|
||||
let r = "";
|
||||
for (var i = 0; i < x.length; i++) {
|
||||
var c = x[i];
|
||||
let c = x[i];
|
||||
if (c == " ") {
|
||||
r += " ";
|
||||
continue;
|
||||
|
@ -113,20 +111,15 @@ function gpsHandleFix(fix) {
|
|||
doBuzz(" .");
|
||||
prev_fix = fix;
|
||||
}
|
||||
if (0) {
|
||||
/* GPS altitude fluctuates a lot, not really usable */
|
||||
alt_adjust = cur_altitude - (fix.alt + geoid_to_sea_level);
|
||||
alt_adjust_mode = "g";
|
||||
}
|
||||
if (1) {
|
||||
let now1 = Date();
|
||||
let now2 = fix.time;
|
||||
var n1 = now1.getMinutes() * 60 + now1.getSeconds();
|
||||
var n2 = now2.getMinutes() * 60 + now2.getSeconds();
|
||||
let n1 = now1.getMinutes() * 60 + now1.getSeconds();
|
||||
let n2 = now2.getMinutes() * 60 + now2.getSeconds();
|
||||
debug2 = "te "+(n2-n1)+"s";
|
||||
}
|
||||
loggps(fix);
|
||||
var d = calcDistance(fix, prev_fix);
|
||||
let d = calcDistance(fix, prev_fix);
|
||||
if (d > 30) {
|
||||
prev_fix = fix;
|
||||
gps_dist += d/1000;
|
||||
|
@ -135,7 +128,7 @@ function gpsHandleFix(fix) {
|
|||
function gpsHandle() {
|
||||
let msg = "";
|
||||
if (!last_restart) {
|
||||
var d = (getTime()-last_pause);
|
||||
let d = (getTime()-last_pause);
|
||||
if (last_fix)
|
||||
msg = "PL"+ fmtTimeDiff(getTime()-last_fix);
|
||||
else
|
||||
|
@ -146,7 +139,7 @@ function gpsHandle() {
|
|||
gpsRestart();
|
||||
}
|
||||
} else {
|
||||
var fix = Bangle.getGPSFix();
|
||||
let fix = Bangle.getGPSFix();
|
||||
if (fix && fix.fix && fix.lat) {
|
||||
gpsHandleFix(fix);
|
||||
msg = fix.speed.toFixed(1) + icon_kph;
|
||||
|
@ -167,8 +160,8 @@ function gpsHandle() {
|
|||
}
|
||||
}
|
||||
|
||||
var d = (getTime()-last_restart);
|
||||
var d2 = (getTime()-last_fstart);
|
||||
let d = (getTime()-last_restart);
|
||||
let d2 = (getTime()-last_fstart);
|
||||
print("gps on, restarted ", d, gps_needed, d2, fix.lat);
|
||||
if (getTime() > gps_speed_limit &&
|
||||
(d > gps_needed || (last_fstart && d2 > 10))) {
|
||||
|
@ -192,8 +185,11 @@ function markNew() {
|
|||
}
|
||||
function markHandle() {
|
||||
let m = cur_mark;
|
||||
var msg = m.name + ">" + fmtTimeDiff(getTime()- m.time);
|
||||
if (m.fix && m.fix.fix) {
|
||||
let msg = m.name + ">";
|
||||
if (m.time) {
|
||||
msg += fmtTimeDiff(getTime()- m.time);
|
||||
}
|
||||
if (prev_fix && prev_fix.fix && m.fix && m.fix.fix) {
|
||||
let s = fmtDist(calcDistance(m.fix, prev_fix)/1000) + icon_km;
|
||||
msg += " " + s;
|
||||
debug = "wp>" + s;
|
||||
|
@ -214,6 +210,34 @@ function entryDone() {
|
|||
in_str = 0;
|
||||
mode = 0;
|
||||
}
|
||||
var waypoints = [], sel_wp = 0;
|
||||
function loadWPs() {
|
||||
waypoints = require("Storage").readJSON(`waypoints.json`)||[{}];
|
||||
print("Have waypoints", waypoints);
|
||||
}
|
||||
function saveWPs() {
|
||||
require("Storage").writeJSON(`waypoints.json`,waypoints);
|
||||
}
|
||||
function selectWP(i) {
|
||||
sel_wp += i;
|
||||
if (sel_wp < 0)
|
||||
sel_wp = 0;
|
||||
if (sel_wp >= waypoints.length)
|
||||
sel_wp = waypoints.length - 1;
|
||||
if (sel_wp < 0) {
|
||||
show("No WPs", 60);
|
||||
}
|
||||
let wp = waypoints[sel_wp];
|
||||
cur_mark = {};
|
||||
cur_mark.name = wp.name;
|
||||
cur_mark.gps_dist = 0; /* HACK */
|
||||
cur_mark.fix = {};
|
||||
cur_mark.fix.fix = 1;
|
||||
cur_mark.fix.lat = wp.lat;
|
||||
cur_mark.fix.lon = wp.lon;
|
||||
show("WP:"+wp.name, 60);
|
||||
print("Select waypoint: ", cur_mark);
|
||||
}
|
||||
function inputHandler(s) {
|
||||
print("Ascii: ", s, s[0], s[1]);
|
||||
if (s[0] == '^') {
|
||||
|
@ -230,9 +254,9 @@ function inputHandler(s) {
|
|||
return;
|
||||
}
|
||||
switch(s) {
|
||||
case 'B':
|
||||
case 'B': {
|
||||
s = ' B';
|
||||
var bat = E.getBattery();
|
||||
let bat = E.getBattery();
|
||||
if (bat > 45)
|
||||
s += 'E';
|
||||
else
|
||||
|
@ -240,6 +264,8 @@ function inputHandler(s) {
|
|||
doBuzz(toMorse(s));
|
||||
show("Bat "+bat+"%", 60);
|
||||
break;
|
||||
}
|
||||
case 'D': selectWP(1); break;
|
||||
case 'F': gpsOff(); show("GPS off", 3); break;
|
||||
case 'G': gpsOn(); gps_limit = getTime() + 60*60*4; show("GPS on", 3); break;
|
||||
case 'I':
|
||||
|
@ -252,15 +278,17 @@ function inputHandler(s) {
|
|||
case 'M': mode = 2; show("M>", 10); cur_mark = markNew(); mode_time = getTime(); break;
|
||||
case 'N': mode = 1; show(">", 10); mode_time = getTime(); break;
|
||||
case 'O': aload("orloj.app.js"); break;
|
||||
case 'R': aload("runplus.app.js"); break;
|
||||
case 'S': gpsOn(); gps_limit = getTime() + 60*30; gps_speed_limit = gps_limit; show("GPS on", 3); break;
|
||||
case 'T':
|
||||
case 'T': {
|
||||
s = ' T';
|
||||
var d = new Date();
|
||||
let d = new Date();
|
||||
s += d.getHours() % 10;
|
||||
s += add0(d.getMinutes());
|
||||
doBuzz(toMorse(s));
|
||||
break;
|
||||
case 'R': aload("run.app.js"); break;
|
||||
}
|
||||
case 'U': selectWP(-1); break;
|
||||
case 'Y': doBuzz(buzz); Bangle.resetCompass(); break;
|
||||
}
|
||||
}
|
||||
|
@ -378,23 +406,22 @@ function loggps(fix) {
|
|||
}
|
||||
function hourly() {
|
||||
print("hourly");
|
||||
var s = ' T';
|
||||
let s = ' T';
|
||||
let bat = E.getBattery();
|
||||
if (bat < 25) {
|
||||
s = ' B';
|
||||
show("Bat "+bat+"%", 60);
|
||||
}
|
||||
if (is_active)
|
||||
doBuzz(toMorse(s));
|
||||
logstamp("");
|
||||
//logstamp("");
|
||||
}
|
||||
function show(msg, timeout) {
|
||||
note = msg;
|
||||
}
|
||||
function fivemin() {
|
||||
print("fivemin");
|
||||
var s = ' B';
|
||||
var bat = E.getBattery();
|
||||
if (bat < 25) {
|
||||
if (is_active)
|
||||
doBuzz(toMorse(s));
|
||||
show("Bat "+bat+"%", 60);
|
||||
}
|
||||
let s = ' B';
|
||||
try {
|
||||
Bangle.getPressure().then((x) => { cur_altitude = x.altitude;
|
||||
cur_temperature = x.temperature; },
|
||||
|
@ -470,8 +497,9 @@ function drawDot(h, d, s) {
|
|||
g.fillCircle(x,y, 10);
|
||||
}
|
||||
function drawBackground() {
|
||||
var acc = Bangle.getAccel();
|
||||
let acc = Bangle.getAccel();
|
||||
is_level = (acc.z < -0.95);
|
||||
Bangle.setCompassPower(!!is_level, "sixths");
|
||||
if (is_level) {
|
||||
let obj = Bangle.getCompass();
|
||||
if (obj) {
|
||||
|
@ -502,13 +530,6 @@ function drawTime(now) {
|
|||
dot = ".";
|
||||
g.drawString(now.getHours() + dot + add0(now.getMinutes()), W, 90);
|
||||
}
|
||||
function adjPressure(a) {
|
||||
var o = Bangle.getOptions();
|
||||
print(o);
|
||||
o.seaLevelPressure = o.seaLevelPressure * m + a;
|
||||
Bangle.setOptions(o);
|
||||
var avr = [];
|
||||
}
|
||||
function draw() {
|
||||
if (disp_mode == 2) {
|
||||
draw_all();
|
||||
|
@ -525,8 +546,12 @@ function draw() {
|
|||
if (gps_on) {
|
||||
msg = gpsHandle();
|
||||
} else {
|
||||
let o = Bangle.getOptions();
|
||||
msg = o.seaLevelPressure.toFixed(1) + "hPa";
|
||||
if (note != "") {
|
||||
msg = note;
|
||||
}
|
||||
}
|
||||
drawBackground();
|
||||
|
||||
let now = new Date();
|
||||
|
@ -551,7 +576,7 @@ function draw() {
|
|||
let alt_adjust = cur_altitude - rest_altitude;
|
||||
let abs = Math.abs(alt_adjust);
|
||||
print("adj", alt_adjust);
|
||||
var o = Bangle.getOptions();
|
||||
let o = Bangle.getOptions();
|
||||
if (abs > 10 && abs < 150) {
|
||||
let a = 0.01;
|
||||
// FIXME: draw is called often compared to alt reading
|
||||
|
@ -588,16 +613,16 @@ function draw_all() {
|
|||
let now = new Date();
|
||||
g.drawString(now.getHours() + ":" + add0(now.getMinutes()) + ":" + add0(now.getSeconds()), 10, 40);
|
||||
|
||||
var acc = Bangle.getAccel();
|
||||
let acc = Bangle.getAccel();
|
||||
let ax = 0 + acc.x, ay = 0.75 + acc.y, az = 0.75 + acc.y;
|
||||
let diff = ax * ax + ay * ay + az * az;
|
||||
diff = diff * 3;
|
||||
if (diff > 1)
|
||||
diff = 1;
|
||||
|
||||
var co = Bangle.getCompass();
|
||||
var step = Bangle.getStepCount();
|
||||
var bat = E.getBattery();
|
||||
let co = Bangle.getCompass();
|
||||
let step = Bangle.getStepCount();
|
||||
let bat = E.getBattery();
|
||||
Bangle.getPressure().then((x) => { alt = x.altitude; temp = x.temperature; },
|
||||
print);
|
||||
|
||||
|
@ -618,7 +643,7 @@ function draw_all() {
|
|||
g.fillCircle(cx + sc * co.dx / 300, cy + sc * co.dz / 400, 5);
|
||||
}
|
||||
if (1) {
|
||||
h = co.heading / 360 * 2 * Math.PI;
|
||||
let h = co.heading / 360 * 2 * Math.PI;
|
||||
g.setColor(0, 0, 0.5);
|
||||
g.fillCircle(cx + sc * Math.sin(h), cy + sc * Math.cos(h), 5);
|
||||
}
|
||||
|
@ -635,10 +660,10 @@ function draw_all() {
|
|||
queueDraw();
|
||||
}
|
||||
function accelTask() {
|
||||
var tm = 100;
|
||||
var acc = Bangle.getAccel();
|
||||
var en = !Bangle.isLocked();
|
||||
var msg;
|
||||
let tm = 100;
|
||||
let acc = Bangle.getAccel();
|
||||
let en = !Bangle.isLocked();
|
||||
let msg = "";
|
||||
if (en && acc.z < -0.95) {
|
||||
msg = "Level";
|
||||
doBuzz(".-..");
|
||||
|
@ -654,14 +679,14 @@ function accelTask() {
|
|||
doBuzz("..-");
|
||||
tm = 3000;
|
||||
}
|
||||
|
||||
print(msg);
|
||||
setTimeout(accelTask, tm);
|
||||
}
|
||||
function buzzTask() {
|
||||
if (buzz != "") {
|
||||
var now = buzz[0];
|
||||
let now = buzz[0];
|
||||
buzz = buzz.substring(1);
|
||||
var dot = 100;
|
||||
let dot = 100;
|
||||
if (now == " ") {
|
||||
setTimeout(buzzTask, 300);
|
||||
} else if (now == ".") {
|
||||
|
@ -675,13 +700,14 @@ function buzzTask() {
|
|||
} else print("Unknown character -- ", now, buzz);
|
||||
}
|
||||
}
|
||||
var last_acc;
|
||||
function aliveTask() {
|
||||
function cmp(s) {
|
||||
let d = acc[s] - last_acc[s];
|
||||
return d < -0.03 || d > 0.03;
|
||||
}
|
||||
// HRM seems to detect hand quite nicely
|
||||
acc = Bangle.getAccel();
|
||||
let acc = Bangle.getAccel();
|
||||
is_active = false;
|
||||
if (cmp("x") || cmp("y") || cmp("z")) {
|
||||
print("active");
|
||||
|
@ -700,7 +726,8 @@ function lockHandler(locked) {
|
|||
}
|
||||
|
||||
function queueDraw() {
|
||||
if (getTime() - last_unlocked > 5*60)
|
||||
let next;
|
||||
if (getTime() - last_unlocked > 3*60)
|
||||
next = 60000;
|
||||
else
|
||||
next = 1000;
|
||||
|
@ -716,8 +743,6 @@ function start() {
|
|||
|
||||
Bangle.on("drag", touchHandler);
|
||||
Bangle.on("lock", lockHandler);
|
||||
if (0)
|
||||
Bangle.on("accel", accelHandler);
|
||||
if (0) {
|
||||
Bangle.setCompassPower(1, "sixths");
|
||||
Bangle.setBarometerPower(1, "sixths");
|
||||
|
@ -729,8 +754,10 @@ function start() {
|
|||
}
|
||||
|
||||
draw();
|
||||
loadWPs();
|
||||
buzzTask();
|
||||
//accelTask();
|
||||
if (0)
|
||||
accelTask();
|
||||
|
||||
if (1) {
|
||||
last_acc = Bangle.getAccel();
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: attempt to import
|
||||
0.02: Minor code improvements
|
||||
0.03: big rewrite, adding time-adjust and altitude-adjust functionality
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "skyspy",
|
||||
"name": "Sky Spy",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"description": "Application for debugging GPS problems",
|
||||
"icon": "app.png",
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -1,20 +1,121 @@
|
|||
/* Sky spy */
|
||||
/* 0 .. DD.ddddd
|
||||
|
||||
/* fmt library v0.1 */
|
||||
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"
|
||||
*/
|
||||
var mode = 1;
|
||||
*/
|
||||
geo_mode : 1,
|
||||
|
||||
init: function() {},
|
||||
fmtDist: function(km) { return km.toFixed(1) + this.icon_km; },
|
||||
fmtSteps: function(n) { return this.fmtDist(0.001 * 0.719 * n); },
|
||||
fmtAlt: function(m) { return m.toFixed(0) + this.icon_alt; },
|
||||
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(pos.lat) + "\n";
|
||||
c = "E";
|
||||
if (x<0) {
|
||||
c = "W";
|
||||
x = -x;
|
||||
}
|
||||
return s + c + this.fmtAngle(pos.lon);
|
||||
},
|
||||
};
|
||||
|
||||
/* gps library v0.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;
|
||||
fix.alt = 200;
|
||||
fix.speed = 5;
|
||||
fix.course = 30;
|
||||
fix.time = Date();
|
||||
fix.satellites = 5;
|
||||
fix.hdop = 12;
|
||||
return fix;
|
||||
}
|
||||
};
|
||||
|
||||
var display = 0;
|
||||
|
||||
var debug = 0;
|
||||
|
||||
var cancel_gps, gps_start;
|
||||
var gps_start;
|
||||
var cur_altitude;
|
||||
|
||||
var wi = 24;
|
||||
var h = 176-wi, w = 176;
|
||||
|
||||
var fix;
|
||||
var adj_time = 0, adj_alt = 0;
|
||||
|
||||
function radA(p) { return p*(Math.PI*2); }
|
||||
function radD(d) { return d*(h/2); }
|
||||
|
@ -27,26 +128,7 @@ function radY(p, d) {
|
|||
return h/2 - Math.cos(a)*radD(d) + wi;
|
||||
}
|
||||
|
||||
function format(x) {
|
||||
switch (mode) {
|
||||
case 0:
|
||||
return "" + x;
|
||||
case 1:
|
||||
d = Math.floor(x);
|
||||
m = x - d;
|
||||
m = m*60;
|
||||
return "" + d + " " + m.toFixed(3) + "'";
|
||||
case 2:
|
||||
d = Math.floor(x);
|
||||
m = x - d;
|
||||
m = m*60;
|
||||
mf = Math.floor(m);
|
||||
s = m - mf;
|
||||
s = s*60;
|
||||
return "" + d + " " + mf + "'" + s.toFixed(0) + '"';
|
||||
}
|
||||
}
|
||||
var qalt = -1;
|
||||
var qalt = -1, min_dalt, max_dalt, step;
|
||||
function resetAlt() {
|
||||
min_dalt = 9999; max_dalt = -9999; step = 0;
|
||||
}
|
||||
|
@ -64,65 +146,96 @@ function calcAlt(alt, cur_altitude) {
|
|||
return ddalt;
|
||||
}
|
||||
function updateGps() {
|
||||
let /*have = false,*/ lat = "lat", lon = "lon", alt = "alt",
|
||||
speed = "speed", hdop = "hdop"; // balt = "balt";
|
||||
let lat = "lat ", alt = "?",
|
||||
speed = "speed ", hdop = "?", adelta = "adelta ",
|
||||
tdelta = "tdelta ";
|
||||
|
||||
if (cancel_gps)
|
||||
return;
|
||||
fix = Bangle.getGPSFix();
|
||||
fix = gps.getGPSFix();
|
||||
if (adj_time) {
|
||||
print("Adjusting time");
|
||||
setTime(fix.time.getTime()/1000);
|
||||
adj_time = 0;
|
||||
}
|
||||
if (adj_alt) {
|
||||
print("Adjust altitude");
|
||||
if (qalt < 5) {
|
||||
let rest_altitude = fix.alt;
|
||||
let alt_adjust = cur_altitude - rest_altitude;
|
||||
let abs = Math.abs(alt_adjust);
|
||||
print("adj", alt_adjust);
|
||||
let o = Bangle.getOptions();
|
||||
if (abs > 10 && abs < 150) {
|
||||
let a = 0.01;
|
||||
// FIXME: draw is called often compared to alt reading
|
||||
if (cur_altitude > rest_altitude)
|
||||
a = -a;
|
||||
o.seaLevelPressure = o.seaLevelPressure + a;
|
||||
Bangle.setOptions(o);
|
||||
}
|
||||
msg = o.seaLevelPressure.toFixed(1) + "hPa";
|
||||
print(msg);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
Bangle.getPressure().then((x) => {
|
||||
cur_altitude = x.altitude;
|
||||
}, print);
|
||||
} catch (e) {
|
||||
print("Altimeter error", e);
|
||||
//print("Altimeter error", e);
|
||||
}
|
||||
|
||||
speed = getTime() - gps_start;
|
||||
|
||||
//print(fix);
|
||||
if (fix && fix.time) {
|
||||
tdelta = "" + (getTime() - fix.time.getTime()/1000).toFixed(0);
|
||||
}
|
||||
if (fix && fix.fix && fix.lat) {
|
||||
lat = "" + format(fix.lat);
|
||||
lon = "" + format(fix.lon);
|
||||
alt = "" + fix.alt.toFixed(1);
|
||||
lat = "" + fmt.fmtPos(fix);
|
||||
alt = "" + fix.alt.toFixed(0);
|
||||
adelta = "" + (cur_altitude - fix.alt).toFixed(0);
|
||||
speed = "" + fix.speed.toFixed(1);
|
||||
hdop = "" + fix.hdop.toFixed(1);
|
||||
//have = true;
|
||||
hdop = "" + fix.hdop.toFixed(0);
|
||||
} else {
|
||||
lat = "NO FIX\n"
|
||||
+ "" + (getTime() - gps_start).toFixed(0) + "s "
|
||||
+ sats_used + "/" + snum;
|
||||
if (cur_altitude)
|
||||
adelta = "" + cur_altitude.toFixed(0);
|
||||
}
|
||||
|
||||
let ddalt = calcAlt(alt, cur_altitude);
|
||||
if (display == 1)
|
||||
g.reset().setFont("Vector", 20)
|
||||
.setColor(1,1,1)
|
||||
.fillRect(0, wi, 176, 176)
|
||||
.setColor(0,0,0)
|
||||
.drawString("Acquiring GPS", 0, 30)
|
||||
.drawString(lat, 0, 50)
|
||||
.drawString(lon, 0, 70)
|
||||
.drawString("alt "+alt, 0, 90)
|
||||
.drawString("speed "+speed, 0, 110)
|
||||
.drawString("hdop "+hdop, 0, 130)
|
||||
.drawString("balt" + cur_altitude, 0, 150);
|
||||
|
||||
let msg = "";
|
||||
if (display == 1) {
|
||||
msg = lat +
|
||||
"\ne" + hdop + "m "+tdelta+"s\n" +
|
||||
speed + "km/h\n"+ alt + "m+" + adelta + "\nmsghere";
|
||||
}
|
||||
if (display == 2) {
|
||||
g.reset().setFont("Vector", 20)
|
||||
.setColor(1,1,1)
|
||||
.fillRect(0, wi, 176, 176)
|
||||
.setColor(0,0,0)
|
||||
.drawString("GPS status", 0, 30)
|
||||
.drawString("speed "+speed, 0, 50)
|
||||
.drawString("hdop "+hdop, 0, 70)
|
||||
.drawString("dd "+qalt.toFixed(0) + " (" + ddalt.toFixed(0) + ")", 0, 90)
|
||||
.drawString("alt "+alt, 0, 110)
|
||||
.drawString("balt " + cur_altitude, 0, 130)
|
||||
.drawString(step, 0, 150);
|
||||
/* qalt is altitude quality estimate -- over ten seconds,
|
||||
computes differences between GPS and barometric altitude.
|
||||
The lower the better.
|
||||
|
||||
ddalt is just a debugging -- same estimate, but without
|
||||
waiting 10 seconds, so will be always optimistic at start
|
||||
of the cycle */
|
||||
msg = speed + "km/h\n" +
|
||||
"e"+hdop + "m"
|
||||
+"\ndd "+qalt.toFixed(0) + "\n(" + step + "/" + ddalt.toFixed(0) + ")" +
|
||||
"\n"+alt + "m+" + adelta;
|
||||
}
|
||||
step++;
|
||||
if (step == 10) {
|
||||
qalt = max_dalt - min_dalt;
|
||||
resetAlt();
|
||||
}
|
||||
if (display > 0) {
|
||||
g.reset().setFont("Vector", 31)
|
||||
.setColor(1,1,1)
|
||||
.fillRect(0, wi, 176, 176)
|
||||
.setColor(0,0,0)
|
||||
.drawString(msg, 3, 25);
|
||||
}
|
||||
|
||||
if (debug > 0)
|
||||
print(fix);
|
||||
setTimeout(updateGps, 1000);
|
||||
|
@ -184,7 +297,7 @@ function drawSats(sats) {
|
|||
|
||||
var sats = [];
|
||||
var snum = 0;
|
||||
//var sats_receiving = 0;
|
||||
var sats_used = 0;
|
||||
|
||||
function parseRaw(msg, lost) {
|
||||
if (lost)
|
||||
|
@ -199,6 +312,7 @@ function parseRaw(msg, lost) {
|
|||
if (s[2] == "1") {
|
||||
snum = 0;
|
||||
sats = [];
|
||||
sats_used = 0;
|
||||
}
|
||||
|
||||
let view = 1 * s[3];
|
||||
|
@ -217,6 +331,8 @@ function parseRaw(msg, lost) {
|
|||
sat.ele = 1*s[i++];
|
||||
sat.azi = 1*s[i++];
|
||||
sat.snr = s[i++];
|
||||
if (sat.snr != "")
|
||||
sats_used++;
|
||||
if (debug > 0)
|
||||
print(" ", sat);
|
||||
sats[snum++] = sat;
|
||||
|
@ -231,30 +347,80 @@ function parseRaw(msg, lost) {
|
|||
}
|
||||
}
|
||||
|
||||
function stopGps() {
|
||||
cancel_gps=true;
|
||||
Bangle.setGPSPower(0, "skyspy");
|
||||
}
|
||||
|
||||
function markGps() {
|
||||
cancel_gps = false;
|
||||
Bangle.setGPSPower(1, "skyspy");
|
||||
Bangle.on('GPS-raw', parseRaw);
|
||||
gps_start = getTime();
|
||||
updateGps();
|
||||
}
|
||||
|
||||
function onSwipe(dir) {
|
||||
display = display + 1;
|
||||
if (display == 3)
|
||||
display = 0;
|
||||
function drawMsg(msg) {
|
||||
g.reset().setFont("Vector", 35)
|
||||
.setColor(1,1,1)
|
||||
.fillRect(0, wi, 176, 176)
|
||||
.setColor(0,0,0)
|
||||
.drawString(msg, 5, 30);
|
||||
}
|
||||
function drawBusy() {
|
||||
drawMsg("\n.oO busy");
|
||||
}
|
||||
|
||||
var numScreens = 3;
|
||||
|
||||
function nextScreen() {
|
||||
display = display + 1;
|
||||
if (display == numScreens)
|
||||
display = 0;
|
||||
drawBusy();
|
||||
}
|
||||
|
||||
function prevScreen() {
|
||||
display = display - 1;
|
||||
if (display < 0)
|
||||
display = numScreens - 1;
|
||||
drawBusy();
|
||||
}
|
||||
|
||||
function onSwipe(dir) {
|
||||
nextScreen();
|
||||
}
|
||||
|
||||
var last_b = 0;
|
||||
function touchHandler(d) {
|
||||
let x = Math.floor(d.x);
|
||||
let y = Math.floor(d.y);
|
||||
|
||||
if (d.b != 1 || last_b != 0) {
|
||||
last_b = d.b;
|
||||
return;
|
||||
}
|
||||
last_b = d.b;
|
||||
|
||||
if ((x<h/2) && (y<w/2)) {
|
||||
drawMsg("Clock\nadjust");
|
||||
adj_time = 1;
|
||||
}
|
||||
if ((x>h/2) && (y<w/2)) {
|
||||
drawMsg("Alt\nadjust");
|
||||
adj_alt = 1;
|
||||
}
|
||||
|
||||
if ((x<h/2) && (y>w/2))
|
||||
prevScreen();
|
||||
if ((x>h/2) && (y>w/2))
|
||||
nextScreen();
|
||||
}
|
||||
|
||||
gps.init();
|
||||
fmt.init();
|
||||
|
||||
Bangle.on("drag", touchHandler);
|
||||
Bangle.setUI({
|
||||
mode : "custom",
|
||||
swipe : onSwipe,
|
||||
clock : 0
|
||||
});
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
drawBusy();
|
||||
markGps();
|
||||
|
|
|
@ -7,3 +7,4 @@
|
|||
0.07: Update clock_info to avoid a redraw
|
||||
0.08: Timer ClockInfo now updates once a minute
|
||||
0.09: Timer ClockInfo resets to timer menu when blurred
|
||||
0.10: Timer ClockInfo now uses +- icons, and changes timer from 'T-5 min' to just '5 min' to aid readability
|
|
@ -28,7 +28,7 @@
|
|||
var min = getAlarmMinutes();
|
||||
if(min < 0)
|
||||
return "OFF";
|
||||
return "T-" + String(min)+ " min";
|
||||
return min + " min";
|
||||
}
|
||||
|
||||
function increaseAlarm(t){
|
||||
|
@ -80,7 +80,7 @@
|
|||
offsets.forEach((o, i) => {
|
||||
smpltmrItems.items = smpltmrItems.items.concat({
|
||||
name: null,
|
||||
get: () => ({ text: (o > 0 ? "+" : "") + o + " min.", img: smpltmrItems.img }),
|
||||
get: () => ({ text: (o > 0 ? "+" : "") + o + " min", img: (o>0)?atob("GBiBAAB+AAB+AAAYAAAYAAB+AA3/sA+B8A4AcAwAMBgYGBgYGDAYDDAYDDH/jDH/jDAYDDAYDBgYGBgYGAwAMA4AcAeB4AH/gAB+AA=="):atob("GBiBAAB+AAB+AAAYAAAYAAB+AA3/sA+B8A4AcAwAMBgAGBgAGDAADDAADDH/jDH/jDAADDAADBgAGBgAGAwAMA4AcAeB4AH/gAB+AA==") }),
|
||||
show: function() { },
|
||||
hide: function() { },
|
||||
blur: restoreMainItem,
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "smpltmr",
|
||||
"name": "Simple Timer",
|
||||
"shortName": "Simple Timer",
|
||||
"version": "0.09",
|
||||
"version": "0.10",
|
||||
"description": "A very simple app to start a timer.",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,alarm,timer,clkinfo",
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,30 @@
|
|||
# Stopwatch with split times
|
||||
|
||||
A basic stopwatch with support for split times.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
Implemented:
|
||||
|
||||
- Start stopwatch
|
||||
- Stop stopwatch
|
||||
- Show split times
|
||||
- Reset stopwatch
|
||||
- Keep display unlocked
|
||||
|
||||
Future:
|
||||
|
||||
- Save state and restore running stopwatch when it reopens
|
||||
- View all split times
|
||||
- Duplicate Start/Stop and/or Reset/Split button on the physical button
|
||||
- Settings, e.g. what the physical button does, and whether to keep the backlight on
|
||||
|
||||
## Creator
|
||||
|
||||
James Taylor ([jt-nti](https://github.com/jt-nti))
|
||||
|
||||
## Icons
|
||||
|
||||
The same icons as apps/stopwatch!
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4UA///vvvvEF/muH+cDHgPABf4AElWoKhILClALH1WqAQIWHBYIABwAKEgQKD1WgBYkK1X1r4XHlWtqtVvQLG1XVBYNXHYsC1YJBBoPqC4kKEQILCvQ7EhW1BYdeBYkqytVqwCCQwkqCgILCq4LFIoILCqoLEIwIsBGQJIBBZ+pA4Na0oDBtQLGvSFCBaYjIHYR3CI5AADBYhrCAAaDHAASDGQASGCBYizCAASzFZYQACZYrjCIwb7QHgIkCvQ6EGAWq+tf1QuEGAWqAAQuFEgQKBEQw9DHIwAuA="))
|
|
@ -0,0 +1,167 @@
|
|||
Bangle.loadWidgets();
|
||||
g.clear(true);
|
||||
Bangle.drawWidgets();
|
||||
|
||||
Bangle.setLCDTimeout(undefined);
|
||||
|
||||
let renderIntervalId;
|
||||
let startTime;
|
||||
let stopTime;
|
||||
let subtotal;
|
||||
let currentSplitNumber;
|
||||
let splitStartTime;
|
||||
let splitSubtotal;
|
||||
|
||||
var Layout = require("Layout");
|
||||
var layout = new Layout( {
|
||||
type:"v", c: [
|
||||
{type:"txt", pad:4, font:"20%", label:"", id:"time", fillx:1},
|
||||
{type:"h", c: [
|
||||
{type:"btn", pad:4, font:"6x8:2", label:"Start", id:"startStop", cb: l=>startStop() },
|
||||
{type:"btn", pad:4, font:"6x8:2", label:"Reset", id:"resetSplit", cb: l=>resetSplit() }
|
||||
]},
|
||||
{
|
||||
type:"v", pad:4, c: [
|
||||
{type:"txt", font:"6x8:2", label:"", id:"split", fillx:1},
|
||||
{type:"txt", font:"6x8:2", label:"", id:"prevSplit", fillx:1},
|
||||
]},
|
||||
]
|
||||
}, {
|
||||
lazy: true,
|
||||
back: load,
|
||||
});
|
||||
|
||||
// TODO The code in this function appears in various apps so it might be
|
||||
// nice to add something to the time_utils module. (There is already a
|
||||
// formatDuration function but that doesn't quite work the same way.)
|
||||
const getTime = function(milliseconds) {
|
||||
let hrs = Math.floor(milliseconds/3600000);
|
||||
let mins = Math.floor(milliseconds/60000)%60;
|
||||
let secs = Math.floor(milliseconds/1000)%60;
|
||||
let tnth = Math.floor(milliseconds/100)%10;
|
||||
let text;
|
||||
|
||||
if (hrs === 0) {
|
||||
text = ("0"+mins).slice(-2) + ":" + ("0"+secs).slice(-2) + "." + tnth;
|
||||
} else {
|
||||
text = ("0"+hrs) + ":" + ("0"+mins).slice(-2) + ":" + ("0"+secs).slice(-2);
|
||||
}
|
||||
|
||||
return text;
|
||||
};
|
||||
|
||||
const renderIntervalCallback = function() {
|
||||
if (startTime === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
updateStopwatch();
|
||||
};
|
||||
|
||||
const buzz = function() {
|
||||
Bangle.buzz(50, 0.5);
|
||||
};
|
||||
|
||||
const startStop = function() {
|
||||
buzz();
|
||||
|
||||
if (layout.startStop.label === "Start") {
|
||||
start();
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
};
|
||||
|
||||
const start = function() {
|
||||
if (stopTime === undefined) {
|
||||
startTime = Date.now();
|
||||
splitStartTime = startTime;
|
||||
subtotal = 0;
|
||||
splitSubtotal = 0;
|
||||
currentSplitNumber = 1;
|
||||
} else {
|
||||
subtotal += stopTime - startTime;
|
||||
splitSubtotal += stopTime - splitStartTime;
|
||||
startTime = Date.now();
|
||||
splitStartTime = startTime;
|
||||
stopTime = undefined;
|
||||
}
|
||||
|
||||
layout.startStop.label = "Stop";
|
||||
layout.resetSplit.label = "Split";
|
||||
updateStopwatch();
|
||||
|
||||
renderIntervalId = setInterval(renderIntervalCallback, 100);
|
||||
};
|
||||
|
||||
const stop = function() {
|
||||
stopTime = Date.now();
|
||||
|
||||
layout.startStop.label = "Start";
|
||||
layout.resetSplit.label = "Reset";
|
||||
updateStopwatch();
|
||||
|
||||
if (renderIntervalId !== undefined) {
|
||||
clearInterval(renderIntervalId);
|
||||
renderIntervalId = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const resetSplit = function() {
|
||||
buzz();
|
||||
|
||||
if (layout.resetSplit.label === "Reset") {
|
||||
reset();
|
||||
} else {
|
||||
split();
|
||||
}
|
||||
};
|
||||
|
||||
const reset = function() {
|
||||
layout.startStop.label = "Start";
|
||||
layout.resetSplit.label = "Reset";
|
||||
layout.split.label = "";
|
||||
layout.prevSplit.label = "";
|
||||
|
||||
startTime = undefined;
|
||||
stopTime = undefined;
|
||||
subtotal = 0;
|
||||
currentSplitNumber = 1;
|
||||
splitStartTime = undefined;
|
||||
splitSubtotal = 0;
|
||||
|
||||
updateStopwatch();
|
||||
};
|
||||
|
||||
const split = function() {
|
||||
const splitTime = Date.now() - splitStartTime + splitSubtotal;
|
||||
layout.prevSplit.label = "#" + currentSplitNumber + " " + getTime(splitTime);
|
||||
|
||||
splitStartTime = Date.now();
|
||||
splitSubtotal = 0;
|
||||
currentSplitNumber++;
|
||||
|
||||
updateStopwatch();
|
||||
};
|
||||
|
||||
const updateStopwatch = function() {
|
||||
let elapsedTime;
|
||||
|
||||
if (startTime === undefined) {
|
||||
elapsedTime = 0;
|
||||
} else {
|
||||
elapsedTime = Date.now() - startTime + subtotal;
|
||||
}
|
||||
|
||||
layout.time.label = getTime(elapsedTime);
|
||||
|
||||
if (splitStartTime !== undefined) {
|
||||
const splitTime = Date.now() - splitStartTime + splitSubtotal;
|
||||
layout.split.label = "#" + currentSplitNumber + " " + getTime(splitTime);
|
||||
}
|
||||
|
||||
layout.render();
|
||||
// layout.debug();
|
||||
};
|
||||
|
||||
updateStopwatch();
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,16 @@
|
|||
{ "id": "splitsw",
|
||||
"name": "Stopwatch with split times",
|
||||
"shortName":"Stopwatch",
|
||||
"version":"0.01",
|
||||
"description": "A basic stopwatch with support for split times",
|
||||
"icon": "app.png",
|
||||
"tags": "tool,outdoors,health",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"screenshots": [{ "url": "screenshot.png" }],
|
||||
"allow_emulator": true,
|
||||
"storage": [
|
||||
{"name":"splitsw.app.js","url":"app.js"},
|
||||
{"name":"splitsw.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 2.9 KiB |
|
@ -0,0 +1 @@
|
|||
0.01: Initial version
|
|
@ -0,0 +1,55 @@
|
|||
# Timestamp Log
|
||||
|
||||
Timestamp Log provides a convenient way to record points in time for later reference. Each time a button is tapped a date/time-stamped marker is logged. By default up to 30 entries can be stored at once; this can be increased up to 100 in the settings menu.
|
||||
|
||||

|
||||
|
||||
## Usage and controls
|
||||
|
||||
When the app starts you will see the log display. Initially the log is empty. The large button on the bottom left displays the current time and will add a date- and time-stamp when tapped. Each tap of the button adds a new entry to the bottom of the log. The small button on the bottom right opens the app settings menu.
|
||||
|
||||
If the log contains more entries than can be displayed at once, swiping up and down will move through the entries one screenfull at a time.
|
||||
|
||||
To delete an individual entry, display it on the screen and then tap on it. The entry's position in the list will be shown along with a Delete button. Tap this button to remove the entry. The Up and Down arrows on the right side of the screen can be used to move between log entries. Further deletions can be made. Finally, click the Back button in the upper-left to finish and return to the main log screen.
|
||||
|
||||
## Settings
|
||||
|
||||
The settings menu provides the following settings:
|
||||
|
||||
### Log
|
||||
|
||||
**Max entries:** Select the maximum number of entries that the log can hold.
|
||||
|
||||
**Auto-delete oldest:** If turned on, adding a log entry when the log is full will cause the oldest entry to automatically be deleted to make room. Otherwise, it is not possible to add another log entry until some entries are manually deleted or the “Max entries” setting is increased.
|
||||
|
||||
**Clear log:** Remove all log entries, leaving the log empty.
|
||||
|
||||
### Appearance
|
||||
|
||||
**Log font:** Select the font used to display log entries.
|
||||
|
||||
**Log font H size** and **Log font V size**: Select the horizontal and vertical sizes, respectively, of the font. Reasonable values for bitmapped fonts are 1 or 2 for either setting. For Vector, values around 15 to 25 work best. Setting both sizes the same will display the font with normal proportions; varying the values will change the relative height or width of the font.
|
||||
|
||||
### Button
|
||||
|
||||
You can choose the action that the physical button (Bangle.js v2) performs when the screen is unlocked.
|
||||
|
||||
**Log time:** Add a date/time stamp to the log. Same as tapping the large button on the touch screen.
|
||||
|
||||
**Open settings:** Open this app settings menu.
|
||||
|
||||
**Quit app:** Return to the main clock app.
|
||||
|
||||
**Do nothing:** Perform no action.
|
||||
|
||||
## Web interface
|
||||
|
||||
Currently the web interface displays the list of dates and times, which can be copied and pasted as desired. The log cannot currently be edited with this interface, only displayed.
|
||||
|
||||
## Support
|
||||
|
||||
Issues and questions may be posted at: https://github.com/espruino/BangleApps/issues
|
||||
|
||||
## Creator
|
||||
|
||||
Travis Evans
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEw4cB/4ACBQX48AFDAAUkyVJAQoDCBZACDymSoEAgVJkQRJkskGAuJCJEpBwYDCyIRHpVICI0SogRGqQyEAgdIBwIUEyAODAQkJiVJxBoDwARIgJuBiIUBCIKzKCIOCQYWkCJUkpNQoiMBkARKgmJxUIxMlOgIQIQwOJyNBiKeBCJeRyUokUoGZPYAQMRyVFgiwDAAsGCIlI0GIBYfAAgUB2wECCINJikRCIfbAgVt2DaDCIMiwR9DjdggEDtg5DgTECSQIJDtuAEwYRFSQOSBIUN2xWCgANBgVSAYKSBR4k2AgYRCxQDBSQTGKgVRCISSBoARKxUpCIKSFAA0SqNFCIKSFAA0RlUo0iSHCI0losUSRARFkmo0SSEwAPFeoORkmViiSEiARHxJXB0SSFAgQCByEAggRCqiSEilChEgwUIXgMkBgVKSQmiqFBgkQoMUoArESQdE6Y1FxIRESQco0WIEYkRiQRDSQWRmnTpojEwRhFSQOKEYOJEYkQogRESQNNEZEIPoQUCEYeKkIjEoLFBCIdTEYc0EYsiCIlKpQjCkojCNIYREpMpEYwCCEYoCB0gjBkmEEYImCgQRGyWTNYJECbQQjHJQIDBygjNpSHCEZ0QAYIjODoJHPEAgjDA=="))
|
|
@ -0,0 +1,523 @@
|
|||
const Layout = require('Layout');
|
||||
const locale = require('locale');
|
||||
|
||||
const tsl = require('timestamplog');
|
||||
|
||||
|
||||
// Min number of pixels of movement to recognize a touchscreen drag/swipe
|
||||
const DRAG_THRESHOLD = 30;
|
||||
|
||||
// Width of scroll indicators
|
||||
const SCROLL_BAR_WIDTH = 12;
|
||||
|
||||
|
||||
// Fetch a stringified image
|
||||
function getIcon(id) {
|
||||
if (id == 'add') {
|
||||
// Graphics.createImage(`
|
||||
// XX X X X X
|
||||
// XX X X X X
|
||||
// XXXXXX X X X X
|
||||
// XXXXXX X X X X
|
||||
// XX X X X X
|
||||
// XX X X X X
|
||||
// X XX X X
|
||||
// X X X X
|
||||
// X XX X
|
||||
// X X X
|
||||
// X X
|
||||
// X XX
|
||||
// XXX XX
|
||||
// XXXXX
|
||||
// XXXX
|
||||
// XX
|
||||
// `);
|
||||
return "\0\x17\x10\x81\x000\t\x12`$K\xF0\x91'\xE2D\x83\t\x12\x06$H\x00\xB1 \x01$\x80\x042\x00\b(\x00 \x00A\x80\x01\xCC\x00\x03\xE0\x00\x0F\x00\x00\x18\x00\x00";
|
||||
} else if (id == 'menu') {
|
||||
// Graphics.createImage(`
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// XXXXXXXXXXXXXXXX
|
||||
// XXXXXXXXXXXXXXXX
|
||||
//
|
||||
//
|
||||
// XXXXXXXXXXXXXXXX
|
||||
// XXXXXXXXXXXXXXXX
|
||||
//
|
||||
//
|
||||
// XXXXXXXXXXXXXXXX
|
||||
// XXXXXXXXXXXXXXXX
|
||||
//
|
||||
//
|
||||
// `);
|
||||
return "\0\x10\x10\x81\0\0\0\0\0\0\0\0\0\xFF\xFF\xFF\xFF\0\0\0\0\xFF\xFF\xFF\xFF\0\0\0\0\xFF\xFF\xFF\xFF\0\0\0\0";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// UI layout render callback for log entries
|
||||
function renderLogItem(elem) {
|
||||
if (elem.item) {
|
||||
g.setColor(g.theme.bg)
|
||||
.fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1)
|
||||
.setFont(tsl.fontSpec(tsl.SETTINGS.logFont,
|
||||
tsl.SETTINGS.logFontHSize, tsl.SETTINGS.logFontVSize))
|
||||
.setFontAlign(-1, -1)
|
||||
.setColor(g.theme.fg)
|
||||
.drawLine(elem.x, elem.y, elem.x + elem.w - 1, elem.y)
|
||||
.drawString(locale.date(elem.item.stamp, 1)
|
||||
+ '\n'
|
||||
+ locale.time(elem.item.stamp).trim(),
|
||||
elem.x, elem.y + 1);
|
||||
} else {
|
||||
g.setColor(g.blendColor(g.theme.bg, g.theme.fg, 0.25))
|
||||
.fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Render a scroll indicator
|
||||
// `scroll` format: {
|
||||
// pos: int,
|
||||
// min: int,
|
||||
// max: int,
|
||||
// itemsPerPage: int,
|
||||
// }
|
||||
function renderScrollBar(elem, scroll) {
|
||||
const border = 1;
|
||||
const boxArea = elem.h - 2 * border;
|
||||
const boxSize = E.clip(
|
||||
Math.round(
|
||||
scroll.itemsPerPage / (scroll.max - scroll.min + 1) * (elem.h - 2)
|
||||
),
|
||||
3,
|
||||
boxArea
|
||||
);
|
||||
const boxTop = (scroll.max - scroll.min) ?
|
||||
Math.round(
|
||||
(scroll.pos - scroll.min) / (scroll.max - scroll.min)
|
||||
* (boxArea - boxSize) + elem.y + border
|
||||
) : elem.y + border;
|
||||
|
||||
// Draw border
|
||||
g.setColor(g.theme.fg)
|
||||
.fillRect(elem.x, elem.y, elem.x + elem.w - 1, elem.y + elem.h - 1)
|
||||
// Draw scroll box area
|
||||
.setColor(g.theme.bg)
|
||||
.fillRect(elem.x + border, elem.y + border,
|
||||
elem.x + elem.w - border - 1, elem.y + elem.h - border - 1)
|
||||
// Draw scroll box
|
||||
.setColor(g.blendColor(g.theme.bg, g.theme.fg, 0.5))
|
||||
.fillRect(elem.x + border, boxTop,
|
||||
elem.x + elem.w - border - 1, boxTop + boxSize - 1);
|
||||
}
|
||||
|
||||
// Main app screen interface, launched by calling start()
|
||||
class MainScreen {
|
||||
|
||||
constructor() {
|
||||
// Values set up by start()
|
||||
this.itemsPerPage = null;
|
||||
this.scrollPos = null;
|
||||
this.layout = null;
|
||||
|
||||
// Handlers/listeners
|
||||
this.buttonTimeoutId = null;
|
||||
this.listeners = {};
|
||||
}
|
||||
|
||||
// Launch this UI and make it live
|
||||
start() {
|
||||
this._initLayout();
|
||||
this.layout.clear();
|
||||
this.scroll('b');
|
||||
this.render('buttons');
|
||||
|
||||
this._initTouch();
|
||||
}
|
||||
|
||||
// Stop this UI, shut down all timers/listeners, and otherwise clean up
|
||||
stop() {
|
||||
if (this.buttonTimeoutId) {
|
||||
clearTimeout(this.buttonTimeoutId);
|
||||
this.buttonTimeoutId = null;
|
||||
}
|
||||
|
||||
// Kill layout handlers
|
||||
Bangle.removeListener('drag', this.listeners.drag);
|
||||
Bangle.removeListener('touch', this.listeners.touch);
|
||||
clearWatch(this.listeners.btnWatch);
|
||||
Bangle.setUI();
|
||||
}
|
||||
|
||||
_initLayout() {
|
||||
let layout = new Layout(
|
||||
{type: 'v',
|
||||
c: [
|
||||
// Placeholder to force bottom alignment when there is unused
|
||||
// vertical screen space
|
||||
{type: '', id: 'placeholder', fillx: 1, filly: 1},
|
||||
|
||||
{type: 'h',
|
||||
c: [
|
||||
{type: 'v',
|
||||
id: 'logItems',
|
||||
|
||||
// To be filled in with log item elements once we
|
||||
// determine how many will fit on screen
|
||||
c: [],
|
||||
},
|
||||
{type: 'custom',
|
||||
id: 'logScroll',
|
||||
render: elem => { renderScrollBar(elem, this.scrollBarInfo()); }
|
||||
},
|
||||
],
|
||||
},
|
||||
{type: 'h',
|
||||
id: 'buttons',
|
||||
c: [
|
||||
{type: 'btn', font: '6x8:2', fillx: 1, label: '+ XX:XX', id: 'addBtn',
|
||||
cb: this.addTimestamp.bind(this)},
|
||||
{type: 'btn', font: '6x8:2', label: getIcon('menu'), id: 'menuBtn',
|
||||
cb: () => launchSettingsMenu(returnFromSettings)},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
// Calculate how many log items per page we have space to display
|
||||
layout.update();
|
||||
let availableHeight = layout.placeholder.h;
|
||||
g.setFont(tsl.fontSpec(tsl.SETTINGS.logFont,
|
||||
tsl.SETTINGS.logFontHSize, tsl.SETTINGS.logFontVSize));
|
||||
let logItemHeight = g.getFontHeight() * 2;
|
||||
this.itemsPerPage = Math.max(1,
|
||||
Math.floor(availableHeight / logItemHeight));
|
||||
|
||||
// Populate log items in layout
|
||||
for (let i = 0; i < this.itemsPerPage; i++) {
|
||||
layout.logItems.c.push(
|
||||
{type: 'custom', render: renderLogItem, item: undefined, itemIdx: undefined,
|
||||
fillx: 1, height: logItemHeight}
|
||||
);
|
||||
}
|
||||
layout.logScroll.height = logItemHeight * this.itemsPerPage;
|
||||
layout.logScroll.width = SCROLL_BAR_WIDTH;
|
||||
layout.update();
|
||||
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
// Redraw a particular display `item`, or everything if `item` is falsey
|
||||
render(item) {
|
||||
if (!item || item == 'log') {
|
||||
let layLogItems = this.layout.logItems;
|
||||
let logIdx = this.scrollPos - this.itemsPerPage;
|
||||
for (let elem of layLogItems.c) {
|
||||
logIdx++;
|
||||
elem.item = stampLog.log[logIdx];
|
||||
elem.itemIdx = logIdx;
|
||||
}
|
||||
this.layout.render(layLogItems);
|
||||
this.layout.render(this.layout.logScroll);
|
||||
}
|
||||
|
||||
if (!item || item == 'buttons') {
|
||||
let addBtn = this.layout.addBtn;
|
||||
|
||||
if (!tsl.SETTINGS.rotateLog && stampLog.isFull()) {
|
||||
// Dimmed appearance for unselectable button
|
||||
addBtn.btnFaceCol = g.blendColor(g.theme.bg2, g.theme.bg, 0.5);
|
||||
addBtn.btnBorderCol = g.blendColor(g.theme.fg2, g.theme.bg, 0.5);
|
||||
|
||||
addBtn.label = 'Log full';
|
||||
} else {
|
||||
addBtn.btnFaceCol = g.theme.bg2;
|
||||
addBtn.btnBorderCol = g.theme.fg2;
|
||||
|
||||
addBtn.label = getIcon('add') + ' ' + locale.time(new Date(), 1).trim();
|
||||
}
|
||||
|
||||
this.layout.render(this.layout.buttons);
|
||||
|
||||
// Auto-update time of day indication on log-add button upon
|
||||
// next minute
|
||||
if (!this.buttonTimeoutId) {
|
||||
this.buttonTimeoutId = setTimeout(
|
||||
() => {
|
||||
this.buttonTimeoutId = null;
|
||||
this.render('buttons');
|
||||
},
|
||||
60000 - (Date.now() % 60000)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_initTouch() {
|
||||
let distanceY = null;
|
||||
|
||||
function dragHandler(ev) {
|
||||
// Handle up/down swipes for scrolling
|
||||
if (ev.b) {
|
||||
if (distanceY === null) {
|
||||
// Drag started
|
||||
distanceY = ev.dy;
|
||||
} else {
|
||||
// Drag in progress
|
||||
distanceY += ev.dy;
|
||||
}
|
||||
} else {
|
||||
// Drag released
|
||||
distanceY = null;
|
||||
}
|
||||
if (Math.abs(distanceY) > DRAG_THRESHOLD) {
|
||||
// Scroll threshold reached
|
||||
Bangle.buzz(50, .5);
|
||||
this.scroll(distanceY > 0 ? 'u' : 'd');
|
||||
distanceY = null;
|
||||
}
|
||||
}
|
||||
|
||||
this.listeners.drag = dragHandler.bind(this);
|
||||
Bangle.on('drag', this.listeners.drag);
|
||||
|
||||
function touchHandler(button, xy) {
|
||||
// Handle taps on log entries
|
||||
let logUIItems = this.layout.logItems.c;
|
||||
for (var logUIObj of logUIItems) {
|
||||
if (!xy.type &&
|
||||
logUIObj.x <= xy.x && xy.x < logUIObj.x + logUIObj.w &&
|
||||
logUIObj.y <= xy.y && xy.y < logUIObj.y + logUIObj.h &&
|
||||
logUIObj.item) {
|
||||
switchUI(new LogEntryScreen(stampLog, logUIObj.itemIdx));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.listeners.touch = touchHandler.bind(this);
|
||||
Bangle.on('touch', this.listeners.touch);
|
||||
|
||||
function buttonHandler() {
|
||||
let act = tsl.SETTINGS.buttonAction;
|
||||
if (act == 'Log time') {
|
||||
if (currentUI != mainUI) {
|
||||
switchUI(mainUI);
|
||||
}
|
||||
mainUI.addTimestamp();
|
||||
} else if (act == 'Open settings') {
|
||||
launchSettingsMenu(returnFromSettings);
|
||||
} else if (act == 'Quit app') {
|
||||
Bangle.showClock();
|
||||
}
|
||||
}
|
||||
|
||||
this.listeners.btnWatch = setWatch(buttonHandler, BTN,
|
||||
{edge: 'falling', debounce: 50, repeat: true});
|
||||
}
|
||||
|
||||
// Add current timestamp to log if possible and update UI display
|
||||
addTimestamp() {
|
||||
if (tsl.SETTINGS.rotateLog || !stampLog.isFull()) {
|
||||
stampLog.addEntry();
|
||||
this.scroll('b');
|
||||
this.render('buttons');
|
||||
}
|
||||
}
|
||||
|
||||
// Get scroll information for log display
|
||||
scrollInfo() {
|
||||
return {
|
||||
pos: this.scrollPos,
|
||||
min: (stampLog.log.length - 1) % this.itemsPerPage,
|
||||
max: stampLog.log.length - 1,
|
||||
itemsPerPage: this.itemsPerPage
|
||||
};
|
||||
}
|
||||
|
||||
// Like scrollInfo, but adjust the data so as to suggest scrollbar
|
||||
// geometry that accurately reflects the nature of the scrolling
|
||||
// (page by page rather than item by item)
|
||||
scrollBarInfo() {
|
||||
const info = this.scrollInfo();
|
||||
|
||||
function toPage(scrollPos) {
|
||||
return Math.floor(scrollPos / info.itemsPerPage);
|
||||
}
|
||||
|
||||
return {
|
||||
// Define 1 "screenfull" as the unit here
|
||||
itemsPerPage: 1,
|
||||
pos: toPage(info.pos),
|
||||
min: toPage(info.min),
|
||||
max: toPage(info.max),
|
||||
};
|
||||
}
|
||||
|
||||
// Scroll display in given direction or to given position:
|
||||
// 'u': up, 'd': down, 't': to top, 'b': to bottom
|
||||
scroll(how) {
|
||||
let scroll = this.scrollInfo();
|
||||
|
||||
if (how == 'u') {
|
||||
this.scrollPos -= scroll.itemsPerPage;
|
||||
} else if (how == 'd') {
|
||||
this.scrollPos += scroll.itemsPerPage;
|
||||
} else if (how == 't') {
|
||||
this.scrollPos = scroll.min;
|
||||
} else if (how == 'b') {
|
||||
this.scrollPos = scroll.max;
|
||||
}
|
||||
|
||||
this.scrollPos = E.clip(this.scrollPos, scroll.min, scroll.max);
|
||||
|
||||
this.render('log');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Log entry screen interface, launched by calling start()
|
||||
class LogEntryScreen {
|
||||
|
||||
constructor(stampLog, logIdx) {
|
||||
this.logIdx = logIdx;
|
||||
this.logItem = stampLog.log[logIdx];
|
||||
|
||||
this.defaultFont = tsl.fontSpec(
|
||||
tsl.SETTINGS.logFont, tsl.SETTINGS.logFontHSize, tsl.SETTINGS.logFontVSize);
|
||||
}
|
||||
|
||||
start() {
|
||||
this._initLayout();
|
||||
this.layout.clear();
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
stop() {
|
||||
Bangle.setUI();
|
||||
}
|
||||
|
||||
back() {
|
||||
this.stop();
|
||||
switchUI(mainUI);
|
||||
}
|
||||
|
||||
_initLayout() {
|
||||
let layout = new Layout(
|
||||
{type: 'v',
|
||||
c: [
|
||||
{type: 'txt', font: this.defaultFont, id: 'entryno', label: 'Entry ?/?'},
|
||||
{type: 'txt', font: this.defaultFont, id: 'date', label: '?'},
|
||||
{type: 'txt', font: this.defaultFont, id: 'time', label: '?'},
|
||||
{type: '', id: 'placeholder', fillx: 1, filly: 1},
|
||||
{type: 'btn', font: '12x20', label: 'Delete',
|
||||
cb: this.delLogItem.bind(this)},
|
||||
],
|
||||
},
|
||||
{
|
||||
back: this.back.bind(this),
|
||||
btns: [
|
||||
{label: '<', cb: this.prevLogItem.bind(this)},
|
||||
{label: '>', cb: this.nextLogItem.bind(this)},
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
layout.update();
|
||||
this.layout = layout;
|
||||
}
|
||||
|
||||
render(item) {
|
||||
this.layout.clear();
|
||||
this.layout.render();
|
||||
}
|
||||
|
||||
refresh() {
|
||||
this.logItem = stampLog.log[this.logIdx];
|
||||
this.layout.entryno.label = 'Entry ' + (this.logIdx+1) + '/' + stampLog.log.length;
|
||||
this.layout.date.label = locale.date(this.logItem.stamp, 1);
|
||||
this.layout.time.label = locale.time(this.logItem.stamp).trim();
|
||||
this.layout.update();
|
||||
this.render();
|
||||
}
|
||||
|
||||
prevLogItem() {
|
||||
this.logIdx = this.logIdx ? this.logIdx-1 : stampLog.log.length-1;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
nextLogItem() {
|
||||
this.logIdx = this.logIdx == stampLog.log.length-1 ? 0 : this.logIdx+1;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
delLogItem() {
|
||||
stampLog.deleteEntries([this.logItem]);
|
||||
if (!stampLog.log.length) {
|
||||
this.back();
|
||||
return;
|
||||
} else if (this.logIdx > stampLog.log.length - 1) {
|
||||
this.logIdx = stampLog.log.length - 1;
|
||||
}
|
||||
|
||||
// Create a brief “blink” on the screen to provide user feedback
|
||||
// that the deletion has been performed
|
||||
this.layout.clear();
|
||||
setTimeout(this.refresh.bind(this), 250);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function switchUI(newUI) {
|
||||
currentUI.stop();
|
||||
currentUI = newUI;
|
||||
currentUI.start();
|
||||
}
|
||||
|
||||
|
||||
function saveErrorAlert() {
|
||||
currentUI.stop();
|
||||
// Not `showAlert` because the icon plus message don't fit the
|
||||
// screen well
|
||||
E.showPrompt(
|
||||
'Trouble saving timestamp log; data may be lost!',
|
||||
{title: "Can't save log",
|
||||
buttons: {'Ok': true}}
|
||||
).then(currentUI.start.bind(currentUI));
|
||||
}
|
||||
|
||||
|
||||
function launchSettingsMenu(backCb) {
|
||||
currentUI.stop();
|
||||
stampLog.save();
|
||||
tsl.launchSettingsMenu(backCb);
|
||||
}
|
||||
|
||||
function returnFromSettings() {
|
||||
// Reload stampLog to pick up any changes made from settings UI
|
||||
stampLog = loadStampLog();
|
||||
currentUI.start();
|
||||
}
|
||||
|
||||
|
||||
function loadStampLog() {
|
||||
// Create a StampLog object with its data loaded from storage
|
||||
return new tsl.StampLog(tsl.LOG_FILENAME, tsl.SETTINGS.maxLogLength);
|
||||
}
|
||||
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
|
||||
var stampLog = loadStampLog();
|
||||
E.on('kill', stampLog.save.bind(stampLog));
|
||||
stampLog.on('saveError', saveErrorAlert);
|
||||
|
||||
var currentUI = new MainScreen(stampLog);
|
||||
var mainUI = currentUI;
|
||||
currentUI.start();
|
After Width: | Height: | Size: 851 B |
|
@ -0,0 +1,31 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
<div id="content">Loading...</div>
|
||||
<script>
|
||||
function onInit() {
|
||||
Util.readStorageJSON('timestamplog.json', stampLog => {
|
||||
document.getElementById("content").innerHTML = HTMLStampLog(stampLog);
|
||||
});
|
||||
}
|
||||
|
||||
function HTMLStampLog(stampLog) {
|
||||
html = ''
|
||||
if (stampLog !== undefined && stampLog.length) {
|
||||
html += '<ol>';
|
||||
for (const logItem of stampLog) {
|
||||
html += ('<li>' + new Date(logItem.stamp).toLocaleString() + '</li>');
|
||||
}
|
||||
html += '</ol>';
|
||||
} else {
|
||||
html += '(empty)';
|
||||
}
|
||||
console.log(html);
|
||||
return html;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,248 @@
|
|||
const storage = require('Storage');
|
||||
|
||||
// Storage filenames
|
||||
|
||||
const LOG_FILENAME = 'timestamplog.json';
|
||||
const SETTINGS_FILENAME = 'timestamplog.settings.json';
|
||||
|
||||
|
||||
// Settings
|
||||
|
||||
const SETTINGS = Object.assign({
|
||||
logFont: '12x20',
|
||||
logFontHSize: 1,
|
||||
logFontVSize: 1,
|
||||
maxLogLength: 30,
|
||||
rotateLog: false,
|
||||
buttonAction: 'Log time',
|
||||
}, storage.readJSON(SETTINGS_FILENAME, true) || {});
|
||||
|
||||
const SETTINGS_BUTTON_ACTION = [
|
||||
'Log time',
|
||||
'Open settings',
|
||||
'Quit app',
|
||||
'Do nothing',
|
||||
];
|
||||
|
||||
|
||||
function fontSpec(name, hsize, vsize) {
|
||||
return name + ':' + hsize + 'x' + vsize;
|
||||
}
|
||||
|
||||
|
||||
//// Data models ////
|
||||
|
||||
// High-level timestamp log object that provides an interface to the
|
||||
// UI for managing log entries and automatically loading/saving
|
||||
// changes to flash storage.
|
||||
class StampLog {
|
||||
constructor(filename, maxLength) {
|
||||
// Name of file to save log to
|
||||
this.filename = filename;
|
||||
// Maximum entries for log before old entries are overwritten with
|
||||
// newer ones
|
||||
this.maxLength = maxLength;
|
||||
|
||||
// `true` when we have changes that need to be saved
|
||||
this.isDirty = false;
|
||||
// Wait at most this many msec upon first data change before
|
||||
// saving (this is to avoid excessive writes to flash if several
|
||||
// changes happen quickly; we wait a little bit so they can be
|
||||
// rolled into a single write)
|
||||
this.saveTimeout = 30000;
|
||||
// setTimeout ID for scheduled save job
|
||||
this.saveId = null;
|
||||
// Underlying raw log data object. Outside this class it's
|
||||
// recommended to use only the class methods to change it rather
|
||||
// than modifying the object directly to ensure that changes are
|
||||
// recognized and saved to storage.
|
||||
this.log = [];
|
||||
|
||||
this.load();
|
||||
}
|
||||
|
||||
// Read in the log data that is currently in storage
|
||||
load() {
|
||||
let log = storage.readJSON(this.filename, true);
|
||||
if (!log) log = [];
|
||||
// Convert stringified datetimes back into Date objects
|
||||
for (let logEntry of log) {
|
||||
logEntry.stamp = new Date(logEntry.stamp);
|
||||
}
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
// Write current log data to storage if anything needs to be saved
|
||||
save() {
|
||||
// Cancel any pending scheduled calls to save()
|
||||
if (this.saveId) {
|
||||
clearTimeout(this.saveId);
|
||||
this.saveId = null;
|
||||
}
|
||||
|
||||
if (this.isDirty) {
|
||||
let logToSave = [];
|
||||
for (let logEntry of this.log) {
|
||||
// Serialize each Date object into an ISO string before saving
|
||||
let newEntry = Object.assign({}, logEntry);
|
||||
newEntry.stamp = logEntry.stamp.toISOString();
|
||||
logToSave.push(newEntry);
|
||||
}
|
||||
|
||||
if (storage.writeJSON(this.filename, logToSave)) {
|
||||
console.log('stamplog: save to storage completed');
|
||||
this.isDirty = false;
|
||||
} else {
|
||||
console.log('stamplog: save to storage FAILED');
|
||||
this.emit('saveError');
|
||||
}
|
||||
} else {
|
||||
console.log('stamplog: skipping save to storage because no changes made');
|
||||
}
|
||||
}
|
||||
|
||||
// Mark log as needing to be (re)written to storage
|
||||
setDirty() {
|
||||
this.isDirty = true;
|
||||
if (!this.saveId) {
|
||||
this.saveId = setTimeout(this.save.bind(this), this.saveTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
// Add a timestamp for the current time to the end of the log
|
||||
addEntry() {
|
||||
// If log full, purge an old entry to make room for new one
|
||||
if (this.maxLength) {
|
||||
while (this.log.length + 1 > this.maxLength) {
|
||||
this.log.shift();
|
||||
}
|
||||
}
|
||||
// Add new entry
|
||||
this.log.push({
|
||||
stamp: new Date()
|
||||
});
|
||||
this.setDirty();
|
||||
}
|
||||
|
||||
// Delete the log objects given in the array `entries` from the log
|
||||
deleteEntries(entries) {
|
||||
this.log = this.log.filter(entry => !entries.includes(entry));
|
||||
this.setDirty();
|
||||
}
|
||||
|
||||
// Does the log currently contain the maximum possible number of entries?
|
||||
isFull() {
|
||||
return this.log.length >= this.maxLength;
|
||||
}
|
||||
}
|
||||
|
||||
function launchSettingsMenu(backCb) {
|
||||
const fonts = g.getFonts();
|
||||
const stampLog = new StampLog(LOG_FILENAME, SETTINGS.maxLogLength);
|
||||
|
||||
function saveSettings() {
|
||||
console.log('Saving timestamp log and settings');
|
||||
stampLog.save();
|
||||
if (!storage.writeJSON(SETTINGS_FILENAME, SETTINGS)) {
|
||||
E.showAlert('Trouble saving settings');
|
||||
}
|
||||
}
|
||||
E.on('kill', saveSettings);
|
||||
|
||||
function endMenu() {
|
||||
saveSettings();
|
||||
E.removeListener('kill', saveSettings);
|
||||
backCb();
|
||||
}
|
||||
|
||||
function topMenu() {
|
||||
E.showMenu({
|
||||
'': {
|
||||
title: 'Stamplog',
|
||||
back: endMenu,
|
||||
},
|
||||
'Log': logMenu,
|
||||
'Appearance': appearanceMenu,
|
||||
'Button': {
|
||||
value: SETTINGS_BUTTON_ACTION.indexOf(SETTINGS.buttonAction),
|
||||
min: 0, max: SETTINGS_BUTTON_ACTION.length - 1,
|
||||
format: v => SETTINGS_BUTTON_ACTION[v],
|
||||
onchange: v => {
|
||||
SETTINGS.buttonAction = SETTINGS_BUTTON_ACTION[v];
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function logMenu() {
|
||||
E.showMenu({
|
||||
'': {
|
||||
title: 'Log',
|
||||
back: topMenu,
|
||||
},
|
||||
'Max entries': {
|
||||
value: SETTINGS.maxLogLength,
|
||||
min: 5, max: 100, step: 5,
|
||||
onchange: v => {
|
||||
SETTINGS.maxLogLength = v;
|
||||
stampLog.maxLength = v;
|
||||
}
|
||||
},
|
||||
'Auto-delete oldest': {
|
||||
value: SETTINGS.rotateLog,
|
||||
onchange: v => {
|
||||
SETTINGS.rotateLog = !SETTINGS.rotateLog;
|
||||
}
|
||||
},
|
||||
'Clear log': doClearLog,
|
||||
});
|
||||
}
|
||||
|
||||
function appearanceMenu() {
|
||||
E.showMenu({
|
||||
'': {
|
||||
title: 'Appearance',
|
||||
back: topMenu,
|
||||
},
|
||||
'Log font': {
|
||||
value: fonts.indexOf(SETTINGS.logFont),
|
||||
min: 0, max: fonts.length - 1,
|
||||
format: v => fonts[v],
|
||||
onchange: v => {
|
||||
SETTINGS.logFont = fonts[v];
|
||||
},
|
||||
},
|
||||
'Log font H size': {
|
||||
value: SETTINGS.logFontHSize,
|
||||
min: 1, max: 50,
|
||||
onchange: v => {
|
||||
SETTINGS.logFontHSize = v;
|
||||
},
|
||||
},
|
||||
'Log font V size': {
|
||||
value: SETTINGS.logFontVSize,
|
||||
min: 1, max: 50,
|
||||
onchange: v => {
|
||||
SETTINGS.logFontVSize = v;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function doClearLog() {
|
||||
E.showPrompt('Erase ALL log entries?', {
|
||||
title: 'Clear log',
|
||||
buttons: {'Erase':1, "Don't":0}
|
||||
}).then((yes) => {
|
||||
if (yes) {
|
||||
stampLog.deleteEntries(stampLog.log);
|
||||
}
|
||||
logMenu();
|
||||
});
|
||||
}
|
||||
|
||||
topMenu();
|
||||
}
|
||||
|
||||
exports = {LOG_FILENAME, SETTINGS_FILENAME, SETTINGS, SETTINGS_BUTTON_ACTION, fontSpec, StampLog,
|
||||
launchSettingsMenu};
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"id": "timestamplog",
|
||||
"name": "Timestamp log",
|
||||
"shortName":"Timestamp log",
|
||||
"icon": "app.png",
|
||||
"version": "0.01",
|
||||
"description": "Conveniently record a series of date/time stamps",
|
||||
"screenshots": [ {"url": "screenshot.png" } ],
|
||||
"readme": "README.md",
|
||||
"tags": "timestamp, log",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"interface": "interface.html",
|
||||
"storage": [
|
||||
{"name": "timestamplog.app.js", "url": "app.js"},
|
||||
{"name": "timestamplog.img", "url": "app-icon.js", "evaluate": true},
|
||||
{"name": "timestamplog", "url": "lib.js"},
|
||||
{"name": "timestamplog.settings.js", "url": "settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name": "timestamplog.settings"},
|
||||
{"name": "timestamplog.json"}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1,7 @@
|
|||
const tsl = require('timestamplog');
|
||||
|
||||
(
|
||||
function(backCb) {
|
||||
tsl.launchSettingsMenu(backCb);
|
||||
}
|
||||
);
|
|
@ -1 +1,2 @@
|
|||
0.01: New Clock!
|
||||
0.02: Clockinfos now save under correct name, and wrap correctly to >1 line
|
|
@ -134,8 +134,8 @@ for (var i=0;i<10;i++)
|
|||
if (g.stringWidth(txt) > options.w) // if too big, smaller font
|
||||
g.setFont("LECO1976Regular14");
|
||||
if (g.stringWidth(txt) > options.w) {// if still too big, split to 2 lines
|
||||
var l = g.wrapString(txt, options.w);
|
||||
txt = l.slice(0,2).join("\n") + (l.length>2)?"...":"";
|
||||
var l = g.wrapString(txt, options.w-4);
|
||||
txt = l.slice(0,2).join("\n") + ((l.length>2)?"...":"");
|
||||
}
|
||||
var x = options.x+options.w/2, y = options.y+54;
|
||||
g.setColor(g.theme.bg).drawString(txt, x-2, y). // draw the text background
|
||||
|
@ -147,12 +147,12 @@ for (var i=0;i<10;i++)
|
|||
};
|
||||
|
||||
clockInfoMenuA = require("clock_info").addInteractive(clockInfoItems, {
|
||||
app:"pebblepp",
|
||||
app:"twotwoclock",
|
||||
x : g.getWidth()-clockInfoW, y: 0, w: clockInfoW, h:clockInfoH,
|
||||
draw : clockInfoDraw
|
||||
});
|
||||
clockInfoMenuB = require("clock_info").addInteractive(clockInfoItems, {
|
||||
app:"pebblepp",
|
||||
app:"twotwoclock",
|
||||
x : g.getWidth()-clockInfoW, y: clockInfoH, w: clockInfoW, h:clockInfoH,
|
||||
draw : clockInfoDraw
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{ "id": "twotwoclock",
|
||||
"name": "TwoTwo Clock",
|
||||
"shortName":"22 Clock",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "A clock with the time split over two lines, with custom backgrounds and two ClockInfos",
|
||||
"icon": "icon.png",
|
||||
"type": "clock",
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
0.01: Initial fork from hwid_a_battery_widget
|
||||
0.02: Show battery percentage (instead of power) if charging
|
||||
0.03: Use `power_usage` module
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"name": "Battery power and percentage widget",
|
||||
"shortName": "Batt Pwr",
|
||||
"icon": "widget.png",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"type": "widget",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
|
|
|
@ -20,13 +20,7 @@
|
|||
function draw() {
|
||||
var x = this.x;
|
||||
var y = this.y;
|
||||
var batt = E.getBattery();
|
||||
var pwr = E.getPowerUsage();
|
||||
var usage = 0;
|
||||
for (var key in pwr.device) {
|
||||
if (!/^(LCD|LED)/.test(key))
|
||||
usage += pwr.device[key];
|
||||
}
|
||||
var _a = require("power_usage").get(), usage = _a.usage, hrsLeft = _a.hrsLeft, batt = _a.batt;
|
||||
var pwrColour = powerColour(usage);
|
||||
g.reset()
|
||||
.setBgColor(g.theme.bg)
|
||||
|
@ -43,9 +37,8 @@
|
|||
txt = "".concat(batt, "%");
|
||||
}
|
||||
else {
|
||||
var hrs = 175000 * batt / (100 * usage);
|
||||
var days = hrs / 24;
|
||||
txt = days >= 1 ? "".concat(Math.round(Math.min(days, 99)), "d") : "".concat(Math.round(hrs), "h");
|
||||
var days = hrsLeft / 24;
|
||||
txt = days >= 1 ? "".concat(Math.round(Math.min(days, 99)), "d") : "".concat(Math.round(hrsLeft), "h");
|
||||
}
|
||||
var txth = 14;
|
||||
g.setColor(g.theme.fg);
|
||||
|
|
|
@ -22,13 +22,7 @@
|
|||
let x = this.x!;
|
||||
let y = this.y!;
|
||||
|
||||
const batt = E.getBattery();
|
||||
const pwr = E.getPowerUsage();
|
||||
let usage = 0;
|
||||
for(const key in pwr.device){
|
||||
if(!/^(LCD|LED)/.test(key))
|
||||
usage += pwr.device[key];
|
||||
}
|
||||
const { usage, hrsLeft, batt } = require("power_usage").get();
|
||||
const pwrColour = powerColour(usage);
|
||||
|
||||
g.reset()
|
||||
|
@ -47,10 +41,8 @@
|
|||
if(showPct || Bangle.isCharging()){
|
||||
txt = `${batt}%`;
|
||||
}else{
|
||||
// 175mAh, scaled based on battery (batt/100), scaled down based on usage
|
||||
const hrs = 175000 * batt / (100 * usage);
|
||||
const days = hrs / 24;
|
||||
txt = days >= 1 ? `${Math.round(Math.min(days, 99))}d` : `${Math.round(hrs)}h`;
|
||||
const days = hrsLeft / 24;
|
||||
txt = days >= 1 ? `${Math.round(Math.min(days, 99))}d` : `${Math.round(hrsLeft)}h`;
|
||||
}
|
||||
|
||||
// draw time remaining, then shade it based on batt %
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
exports.get = function () {
|
||||
var pwr = E.getPowerUsage();
|
||||
var batt = E.getBattery();
|
||||
var usage = 0;
|
||||
for (var key in pwr.device) {
|
||||
if (!key.startsWith("LCD"))
|
||||
usage += pwr.device[key];
|
||||
}
|
||||
var hrsLeft = 175000 * batt / (100 * usage);
|
||||
return {
|
||||
usage: usage,
|
||||
hrsLeft: hrsLeft,
|
||||
batt: batt,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
type Pwr = {
|
||||
usage: number,
|
||||
hrsLeft: number,
|
||||
batt: number, // battery percentage
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
type PowerUsageModule = {
|
||||
get: () => Pwr,
|
||||
};
|
||||
|
||||
exports.get = (): Pwr => {
|
||||
const pwr = E.getPowerUsage();
|
||||
const batt = E.getBattery();
|
||||
let usage = 0;
|
||||
for(const key in pwr.device){
|
||||
if(!key.startsWith("LCD"))
|
||||
usage += pwr.device[key as keyof typeof pwr.device]!;
|
||||
}
|
||||
|
||||
// 175mAh, scaled based on battery (batt/100), scaled down based on usage
|
||||
const hrsLeft = 175000 * batt / (100 * usage);
|
||||
|
||||
return {
|
||||
usage,
|
||||
hrsLeft,
|
||||
batt,
|
||||
};
|
||||
};
|
|
@ -316,6 +316,48 @@ type VariableSizeInformation = {
|
|||
more?: VariableSizeInformation;
|
||||
};
|
||||
|
||||
type PowerUsage = {
|
||||
total: number,
|
||||
device: {
|
||||
CPU?: number,
|
||||
UART?: number,
|
||||
PWM?: number,
|
||||
LED1?: number,
|
||||
LED2?: number,
|
||||
LED3?: number,
|
||||
|
||||
// bangle
|
||||
LCD?: number,
|
||||
LCD_backlight?: number,
|
||||
LCD_touch?: number,
|
||||
HRM?: number,
|
||||
GPS?: number,
|
||||
compass?: number,
|
||||
baro?: number,
|
||||
|
||||
// nrf
|
||||
BLE_periph?: number,
|
||||
BLE_central?: number,
|
||||
BLE_advertise?: number,
|
||||
BLE_scan?: number,
|
||||
|
||||
// pixljs
|
||||
//LCD?: number, // (see above)
|
||||
|
||||
// puck
|
||||
mag?: number,
|
||||
accel?: number,
|
||||
|
||||
// jolt
|
||||
driver0?: number,
|
||||
driver1?: number,
|
||||
pin0_internal_resistance?: number,
|
||||
pin2_internal_resistance?: number,
|
||||
pin4_internal_resistance?: number,
|
||||
pin6_internal_resistance?: number,
|
||||
},
|
||||
};
|
||||
|
||||
type PipeOptions = {
|
||||
chunkSize?: number,
|
||||
end?: boolean,
|
||||
|
@ -1014,13 +1056,15 @@ declare class NRF {
|
|||
static eraseBonds(callback?: any): void;
|
||||
|
||||
/**
|
||||
* Get this device's default Bluetooth MAC address.
|
||||
* Get this device's default or current Bluetooth MAC address.
|
||||
* For Puck.js, the last 5 characters of this (e.g. `ee:ff`) are used in the
|
||||
* device's advertised Bluetooth name.
|
||||
*
|
||||
* @param {boolean} current - If true, return the current address rather than the default
|
||||
* @returns {any} MAC address - a string of the form 'aa:bb:cc:dd:ee:ff'
|
||||
* @url http://www.espruino.com/Reference#l_NRF_getAddress
|
||||
*/
|
||||
static getAddress(): any;
|
||||
static getAddress(current: boolean): any;
|
||||
|
||||
/**
|
||||
* Set this device's default Bluetooth MAC address:
|
||||
|
@ -4524,10 +4568,32 @@ declare class Bangle {
|
|||
static dbg(): any;
|
||||
|
||||
/**
|
||||
* Writes a register on the accelerometer
|
||||
* Writes a register on the touch controller
|
||||
*
|
||||
* @param {number} reg
|
||||
* @param {number} data
|
||||
* @url http://www.espruino.com/Reference#l_Bangle_touchWr
|
||||
*/
|
||||
static touchWr(reg: number, data: number): void;
|
||||
|
||||
/**
|
||||
* Reads a register from the touch controller
|
||||
* **Note:** On Espruino 2v06 and before this function only returns a number (`cnt`
|
||||
* is ignored).
|
||||
*
|
||||
* @param {number} reg - Register number to read
|
||||
* @param {number} cnt - If specified, returns an array of the given length (max 128). If not (or 0) it returns a number
|
||||
* @returns {any}
|
||||
* @url http://www.espruino.com/Reference#l_Bangle_touchRd
|
||||
*/
|
||||
static touchRd(reg: number, cnt?: 0): number;
|
||||
static touchRd(reg: number, cnt: number): number[];
|
||||
|
||||
/**
|
||||
* Writes a register on the accelerometer
|
||||
*
|
||||
* @param {number} reg - Register number to write
|
||||
* @param {number} data - An integer value to write to the register
|
||||
* @url http://www.espruino.com/Reference#l_Bangle_accelWr
|
||||
*/
|
||||
static accelWr(reg: number, data: number): void;
|
||||
|
@ -8343,7 +8409,7 @@ declare class E {
|
|||
* E.showScroller({
|
||||
* h : 40, c : 8,
|
||||
* draw : (idx, r) => {
|
||||
* g.setBgColor((idx&1)?"#666":"#999").clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1);
|
||||
* g.setBgColor((idx&1)?"#666":"#CCC").clearRect(r.x,r.y,r.x+r.w-1,r.y+r.h-1);
|
||||
* g.setFont("6x8:2").drawString("Item Number\n"+idx,r.x+10,r.y+4);
|
||||
* },
|
||||
* select : (idx) => console.log("You selected ", idx)
|
||||
|
@ -9482,7 +9548,7 @@ declare class E {
|
|||
* @returns {any} An object detailing power usage in microamps
|
||||
* @url http://www.espruino.com/Reference#l_E_getPowerUsage
|
||||
*/
|
||||
static getPowerUsage(): any;
|
||||
static getPowerUsage(): PowerUsage;
|
||||
|
||||
/**
|
||||
* Decode a UTF8 string.
|
||||
|
@ -11137,21 +11203,21 @@ interface String {
|
|||
* Return the index of substring in this string, or -1 if not found
|
||||
*
|
||||
* @param {any} substring - The string to search for
|
||||
* @param {any} fromIndex - Index to search from
|
||||
* @param {any} [fromIndex] - [optional] Index to search from
|
||||
* @returns {number} The index of the string, or -1 if not found
|
||||
* @url http://www.espruino.com/Reference#l_String_indexOf
|
||||
*/
|
||||
indexOf(substring: any, fromIndex: any): number;
|
||||
indexOf(substring: any, fromIndex?: any): number;
|
||||
|
||||
/**
|
||||
* Return the last index of substring in this string, or -1 if not found
|
||||
*
|
||||
* @param {any} substring - The string to search for
|
||||
* @param {any} fromIndex - Index to search from
|
||||
* @param {any} [fromIndex] - [optional] Index to search from
|
||||
* @returns {number} The index of the string, or -1 if not found
|
||||
* @url http://www.espruino.com/Reference#l_String_lastIndexOf
|
||||
*/
|
||||
lastIndexOf(substring: any, fromIndex: any): number;
|
||||
lastIndexOf(substring: any, fromIndex?: any): number;
|
||||
|
||||
/**
|
||||
* Matches an occurrence `subStr` in the string.
|
||||
|
@ -11206,20 +11272,20 @@ interface String {
|
|||
/**
|
||||
*
|
||||
* @param {number} start - The start character index (inclusive)
|
||||
* @param {any} end - The end character index (exclusive)
|
||||
* @param {any} [end] - [optional] The end character index (exclusive)
|
||||
* @returns {any} The part of this string between start and end
|
||||
* @url http://www.espruino.com/Reference#l_String_substring
|
||||
*/
|
||||
substring(start: number, end: any): any;
|
||||
substring(start: number, end?: any): any;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {number} start - The start character index
|
||||
* @param {any} len - The number of characters
|
||||
* @param {any} [len] - [optional] The number of characters
|
||||
* @returns {any} Part of this string from start for len characters
|
||||
* @url http://www.espruino.com/Reference#l_String_substr
|
||||
*/
|
||||
substr(start: number, len: any): any;
|
||||
substr(start: number, len?: any): any;
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -11286,29 +11352,29 @@ interface String {
|
|||
/**
|
||||
*
|
||||
* @param {any} searchString - The string to search for
|
||||
* @param {number} position - The start character index (or 0 if not defined)
|
||||
* @param {number} [position] - [optional] The start character index (or 0 if not defined)
|
||||
* @returns {boolean} `true` if the given characters are found at the beginning of the string, otherwise, `false`.
|
||||
* @url http://www.espruino.com/Reference#l_String_startsWith
|
||||
*/
|
||||
startsWith(searchString: any, position: number): boolean;
|
||||
startsWith(searchString: any, position?: number): boolean;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} searchString - The string to search for
|
||||
* @param {any} length - The 'end' of the string - if left off the actual length of the string is used
|
||||
* @param {any} [length] - [optional] The 'end' of the string - if left off the actual length of the string is used
|
||||
* @returns {boolean} `true` if the given characters are found at the end of the string, otherwise, `false`.
|
||||
* @url http://www.espruino.com/Reference#l_String_endsWith
|
||||
*/
|
||||
endsWith(searchString: any, length: any): boolean;
|
||||
endsWith(searchString: any, length?: any): boolean;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {any} substring - The string to search for
|
||||
* @param {any} fromIndex - The start character index (or 0 if not defined)
|
||||
* @param {any} [fromIndex] - [optional] The start character index (or 0 if not defined)
|
||||
* @returns {boolean} `true` if the given characters are in the string, otherwise, `false`.
|
||||
* @url http://www.espruino.com/Reference#l_String_includes
|
||||
*/
|
||||
includes(substring: any, fromIndex: any): boolean;
|
||||
includes(substring: any, fromIndex?: any): boolean;
|
||||
|
||||
/**
|
||||
* Repeat this string the given number of times.
|
||||
|
@ -11411,8 +11477,10 @@ interface RegExp {
|
|||
/**
|
||||
* The built-in class for handling Regular Expressions
|
||||
* **Note:** Espruino's regular expression parser does not contain all the features
|
||||
* present in a full ES6 JS engine. However it does contain support for the all the
|
||||
* basics.
|
||||
* present in a full ES6 JS engine. however some parts of the spec are not implemented:
|
||||
* * [Assertions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Assertions) other than `^` and `$`
|
||||
* * [Numeric quantifiers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Quantifiers) (eg `x{3}`)
|
||||
* There's a GitHub issue [concerning RegExp features here](https://github.com/espruino/Espruino/issues/1257)
|
||||
* @url http://www.espruino.com/Reference#RegExp
|
||||
*/
|
||||
declare const RegExp: RegExpConstructor
|
||||
|
|
|
@ -5,3 +5,4 @@ declare function require(moduleName: "sched"): typeof Sched;
|
|||
declare function require(moduleName: "ClockFace"): typeof ClockFace_.ClockFace;
|
||||
declare function require(moduleName: "clock_info"): typeof ClockInfo;
|
||||
declare function require(moduleName: "Layout"): typeof Layout.Layout;
|
||||
declare function require(moduleName: "power_usage"): PowerUsageModule;
|
||||
|
|