1
0
Fork 0

Merge branch 'master' of github.com:targor/BangleApps

master
Targor 2023-05-14 14:03:58 +02:00
commit 818d6ea98e
112 changed files with 2234 additions and 448 deletions

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: Barometer altitude adjustment setting
0.03: Use default Bangle formatter for booleans
0.04: Add options for units in locale and recording GPS

View File

@ -403,6 +403,8 @@ function onGPS(fix) {
if ( sp < 10 ) sp = sp.toFixed(1);
else sp = Math.round(sp);
if (isNaN(sp)) sp = '---';
if (parseFloat(sp) > parseFloat(max.spd) && max.n > 15 ) max.spd = parseFloat(sp);
// Altitude
@ -416,6 +418,12 @@ function onGPS(fix) {
// Age of last fix (secs)
age = Math.max(0,Math.round(getTime())-(lf.time.getTime()/1000));
} else {
// populate spd_unit
if (cfg.spd == 0) {
m = require("locale").speed(0).match(/[0-9,\.]+(.*)/);
cfg.spd_unit = m[1];
}
}
if ( cfg.modeA == 1 ) {
@ -465,7 +473,7 @@ function updateClock() {
// Read settings.
let cfg = require('Storage').readJSON('bikespeedo.json',1)||{};
cfg.spd = 1; // Multiplier for speed unit conversions. 0 = use the locale values for speed
cfg.spd = !cfg.localeUnits; // Multiplier for speed unit conversions. 0 = use the locale values for speed
cfg.spd_unit = 'km/h'; // Displayed speed unit
cfg.alt = 1; // Multiplier for altitude unit conversions. (feet:'0.3048')
cfg.alt_unit = 'm'; // Displayed altitude units ('feet')
@ -499,14 +507,6 @@ function onPressure(dat) {
altiBaro = Number(dat.altitude.toFixed(0)) + Number(cfg.altDiff);
}
Bangle.setBarometerPower(1); // needs some time...
g.clearRect(0,screenYstart,screenW,screenH);
onGPS(lf);
Bangle.setGPSPower(1);
Bangle.on('GPS', onGPS);
Bangle.on('pressure', onPressure);
Bangle.setCompassPower(1);
var CALIBDATA = require("Storage").readJSON("magnav.json",1)||null;
if (!CALIBDATA) calibrateCompass = true;
function Compass_tiltfixread(O,S){
@ -544,11 +544,33 @@ function Compass_reading() {
Compass_heading = Compass_newHeading(d,Compass_heading);
hdngCompass = Compass_heading.toFixed(0);
}
if (!calibrateCompass) setInterval(Compass_reading,200);
setButtons();
if (emulator) setInterval(updateClock, 2000);
else setInterval(updateClock, 10000);
function start() {
Bangle.setBarometerPower(1); // needs some time...
g.clearRect(0,screenYstart,screenW,screenH);
onGPS(lf);
Bangle.setGPSPower(1);
Bangle.on('GPS', onGPS);
Bangle.on('pressure', onPressure);
Bangle.setCompassPower(1);
if (!calibrateCompass) setInterval(Compass_reading,200);
setButtons();
if (emulator) setInterval(updateClock, 2000);
else setInterval(updateClock, 10000);
Bangle.drawWidgets();
}
Bangle.loadWidgets();
Bangle.drawWidgets();
if (cfg.record && WIDGETS["recorder"]) {
WIDGETS["recorder"]
.setRecording(true)
.then(start);
if (cfg.recordStopOnExit)
E.on('kill', () => WIDGETS["recorder"].setRecording(false));
} else {
start();
}

View File

@ -2,7 +2,7 @@
"id": "bikespeedo",
"name": "Bike Speedometer (beta)",
"shortName": "Bike Speedometer",
"version": "0.03",
"version": "0.04",
"description": "Shows GPS speed, GPS heading, Compass heading, GPS altitude and Barometer altitude from internal sources",
"icon": "app.png",
"screenshots": [{"url":"Screenshot.png"}],

View File

@ -11,9 +11,34 @@
'< Back': back,
'< Load Bike Speedometer': ()=>{load('bikespeedo.app.js');},
'Barometer Altitude adjustment' : function() { E.showMenu(altdiffMenu); },
'Kalman Filters' : function() { E.showMenu(kalMenu); }
'Kalman Filters' : function() { E.showMenu(kalMenu); },
'Speed units': {
value: !!settings.localeUnits,
format: b => b ? "Locale" : "km/h",
onchange: b => {
settings.localeUnits = b;
writeSettings();
}
},
};
if (global.WIDGETS && WIDGETS["recorder"]) {
appMenu[/*LANG*/"Record rides"] = {
value : !!settings.record,
onchange : v => {
settings.record = v;
writeSettings();
}
};
appMenu[/*LANG*/"Stop record on exit"] = {
value : !!settings.recordStopOnExit,
onchange : v => {
settings.recordStopOnExit = v;
writeSettings();
}
};
}
const altdiffMenu = {
'': { 'title': 'Altitude adjustment' },
'< Back': function() { E.showMenu(appMenu); },

View File

@ -13,3 +13,4 @@
0.12: Mark dated events on a day
0.13: Switch to swipe left/right for month and up/down for year selection
Display events for current month on touch
0.14: Add support for holidays

View File

@ -10,6 +10,7 @@ Basic calendar
- Swipe down (Bangle.js 2 only) to go to the next year
- Touch to display events for current month
- Press the button (button 3 on Bangle.js 1) to exit
- Holidays have same color as weekends and can be edited with the 'Download'-interface, e.g. by uploading an iCalendar file.
## Settings

View File

@ -16,6 +16,7 @@ const white = "#ffffff";
const red = "#d41706";
const blue = "#0000ff";
const yellow = "#ffff00";
const cyan = "#00ffff";
let bgColor = color4;
let bgColorMonth = color1;
let bgColorDow = color2;
@ -23,6 +24,7 @@ let bgColorWeekend = color3;
let fgOtherMonth = gray1;
let fgSameMonth = white;
let bgEvent = blue;
let bgOtherEvent = "#ff8800";
const eventsPerDay=6; // how much different events per day we can display
const date = new Date();
@ -36,9 +38,17 @@ const events = (require("Storage").readJSON("sched.json",1) || []).filter(a => a
date.setHours(time.h);
date.setMinutes(time.m);
date.setSeconds(time.s);
return {date: date, msg: a.msg};
return {date: date, msg: a.msg, type: "e"};
});
// add holidays & other events
(require("Storage").readJSON("calendar.days.json",1) || []).forEach(d => {
const date = new Date(d.date);
const o = {date: date, msg: d.name, type: d.type};
if (d.repeat) {
o.repeat = d.repeat;
}
events.push(o);
});
events.sort((a,b) => a.date - b.date);
if (settings.ndColors === undefined) {
settings.ndColors = !g.theme.dark;
@ -52,68 +62,16 @@ if (settings.ndColors === true) {
fgOtherMonth = blue;
fgSameMonth = black;
bgEvent = color2;
bgOtherEvent = cyan;
}
function getDowLbls(locale) {
let dowLbls;
//TODO: Find some clever way to generate this programmatically from locale lib
switch (locale) {
case "de_AT":
case "de_CH":
case "de_DE":
if (startOnSun) {
dowLbls = ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"];
} else {
dowLbls = ["Mo", "Di", "Mi", "Do", "Fr", "Sa", "So"];
}
break;
case "nl_NL":
if (startOnSun) {
dowLbls = ["zo", "ma", "di", "wo", "do", "vr", "za"];
} else {
dowLbls = ["ma", "di", "wo", "do", "vr", "za", "zo"];
}
break;
case "fr_BE":
case "fr_CH":
case "fr_FR":
if (startOnSun) {
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
} else {
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
}
break;
case "sv_SE":
if (startOnSun) {
dowLbls = ["Di", "Lu", "Ma", "Me", "Je", "Ve", "Sa"];
} else {
dowLbls = ["Lu", "Ma", "Me", "Je", "Ve", "Sa", "Di"];
}
break;
case "it_CH":
case "it_IT":
if (startOnSun) {
dowLbls = ["Do", "Lu", "Ma", "Me", "Gi", "Ve", "Sa"];
} else {
dowLbls = ["Lu", "Ma", "Me", "Gi", "Ve", "Sa", "Do"];
}
break;
case "oc_FR":
if (startOnSun) {
dowLbls = ["dg", "dl", "dm", "dc", "dj", "dv", "ds"];
} else {
dowLbls = ["dl", "dm", "dc", "dj", "dv", "ds", "dg"];
}
break;
default:
if (startOnSun) {
dowLbls = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"];
} else {
dowLbls = ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"];
}
break;
}
return dowLbls;
let days = startOnSun ? [0, 1, 2, 3, 4, 5, 6] : [1, 2, 3, 4, 5, 6, 0];
const d = new Date();
return days.map(i => {
d.setDate(d.getDate() + (i + 7 - d.getDay()) % 7);
return require("locale").dow(d, 1);
});
}
function sameDay(d1, d2) {
@ -206,8 +164,13 @@ function drawCalendar(date) {
weekBeforeMonth.setDate(weekBeforeMonth.getDate() - 7);
const week2AfterMonth = new Date(date.getFullYear(), date.getMonth()+1, 0);
week2AfterMonth.setDate(week2AfterMonth.getDate() + 14);
events.forEach(ev => {
if (ev.repeat === "y") {
ev.date.setFullYear(ev.date.getMonth() < 6 ? week2AfterMonth.getFullYear() : weekBeforeMonth.getFullYear());
}
});
const eventsThisMonth = events.filter(ev => ev.date > weekBeforeMonth && ev.date < week2AfterMonth);
eventsThisMonth.sort((a,b) => a.date - b.date);
let i = 0;
for (y = 0; y < rowN - 1; y++) {
for (x = 0; x < colN; x++) {
@ -220,6 +183,33 @@ function drawCalendar(date) {
const y1 = y * rowH + headerH + rowH;
const x2 = x * colW + colW;
const y2 = y * rowH + headerH + rowH + rowH;
if (eventsThisMonth.length > 0) {
// Display events for this day
eventsThisMonth.forEach((ev, idx) => {
if (sameDay(ev.date, curDay)) {
switch(ev.type) {
case "e": // alarm/event
const hour = ev.date.getHours() + ev.date.getMinutes()/60.0;
const slice = hour/24*(eventsPerDay-1); // slice 0 for 0:00 up to eventsPerDay for 23:59
const height = (y2-2) - (y1+2); // height of a cell
const sliceHeight = height/eventsPerDay;
const ystart = (y1+2) + slice*sliceHeight;
g.setColor(bgEvent).fillRect(x1+1, ystart, x2-2, ystart+sliceHeight);
break;
case "h": // holiday
g.setColor(bgColorWeekend).fillRect(x1+1, y1+1, x2-1, y2-1);
break;
case "o": // other
g.setColor(bgOtherEvent).fillRect(x1+1, y1+1, x2-1, y2-1);
break;
}
eventsThisMonth.splice(idx, 1); // this event is no longer needed
}
});
}
if (isToday) {
g.setColor(red);
g.drawRect(x1, y1, x2, y2);
@ -231,23 +221,6 @@ function drawCalendar(date) {
);
}
if (eventsThisMonth.length > 0) {
// Display events for this day
g.setColor(bgEvent);
eventsThisMonth.forEach((ev, idx) => {
if (sameDay(ev.date, curDay)) {
const hour = ev.date.getHours() + ev.date.getMinutes()/60.0;
const slice = hour/24*(eventsPerDay-1); // slice 0 for 0:00 up to eventsPerDay for 23:59
const height = (y2-2) - (y1+2); // height of a cell
const sliceHeight = height/eventsPerDay;
const ystart = (y1+2) + slice*sliceHeight;
g.fillRect(x1+1, ystart, x2-2, ystart+sliceHeight);
eventsThisMonth.splice(idx, 1); // this event is no longer needed
}
});
}
require("Font8x12").add(Graphics);
g.setFont("8x12", fontSize);
g.setColor(day < 50 ? fgOtherMonth : fgSameMonth);
@ -286,10 +259,11 @@ function setUI() {
},
btn: (n) => n === (process.env.HWVERSION === 2 ? 1 : 3) && load(),
touch: (n,e) => {
events.sort((a,b) => a.date - b.date);
const menu = events.filter(ev => ev.date.getFullYear() === date.getFullYear() && ev.date.getMonth() === date.getMonth()).map(e => {
const dateStr = require("locale").date(e.date, 1);
const timeStr = require("locale").time(e.date, 1);
return { title: `${dateStr} ${timeStr}` + (e.msg ? " " + e.msg : "") };
return { title: `${dateStr} ${e.type === "e" ? timeStr : ""}` + (e.msg ? " " + e.msg : "") };
});
if (menu.length === 0) {
menu.push({title: /*LANG*/"No events"});

View File

@ -0,0 +1,234 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
<script src="../../core/lib/interface.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ical.js/1.5.0/ical.min.js"></script>
<script>
let dataElement = document.getElementById("data");
let holidays;
function sameDay(d1, d2) {
return d1.getFullYear() === d2.getFullYear() &&
d1.getMonth() === d2.getMonth() &&
d1.getDate() === d2.getDate();
}
function render() {
holidays.sort((a,b) => new Date(a.date) - new Date(b.date));
document.getElementById('events').innerHTML = "";
holidays.forEach(holiday => {
renderHoliday(holiday);
});
}
function readFile(input) {
document.getElementById('upload').disabled = true;
for(let i=0; i<input.files.length; i++) {
const reader = new FileReader();
reader.addEventListener("load", () => {
const jCalData = ICAL.parse(reader.result);
const comp = new ICAL.Component(jCalData);
// Fetch the VEVENT part
comp.getAllSubcomponents('vevent').forEach(vevent => {
event = new ICAL.Event(vevent);
holidays = holidays.filter(holiday => !sameDay(new Date(holiday.date), event.startDate.toJSDate())); // remove if already exists
const holiday = eventToHoliday(event);
holidays.push(holiday);
});
render();
}, false);
reader.readAsText(input.files[i], "UTF-8");
}
}
function eventToHoliday(event) {
const date = event.startDate.toJSDate();
const holiday = {
date: formatDate(date),
name: event.summary,
type: 'h',
};
return holiday;
}
function formatDate(d) {
return d.getFullYear() + "-" + (d.getMonth() + 1).toString().padStart(2, '0') + "-" + d.getDate().toString().padStart(2, '0');
}
function upload() {
Util.showModal("Saving...");
Util.writeStorage("calendar.days.json", JSON.stringify(holidays), () => {
location.reload(); // reload so we see current data
});
}
function renderHoliday(holiday) {
const localDate = new Date(holiday.date);
const tr = document.createElement('tr');
tr.classList.add('event-row');
const tdTime = document.createElement('td');
tr.appendChild(tdTime);
const inputTime = document.createElement('input');
inputTime.type = "date";
inputTime.classList.add('event-date');
inputTime.classList.add('form-input');
inputTime.value = formatDate(localDate)
inputTime.onchange = (e => {
const date = new Date(inputTime.value);
holiday.date = formatDate(date);
});
tdTime.appendChild(inputTime);
const tdSummary = document.createElement('td');
tr.appendChild(tdSummary);
const inputSummary = document.createElement('input');
inputSummary.type = "text";
inputSummary.classList.add('event-summary');
inputSummary.classList.add('form-input');
inputSummary.maxLength=40;
const summary = (holiday.name?.substring(0, inputSummary.maxLength) || "");
inputSummary.value = summary;
inputSummary.onchange = (e => {
holiday.name = inputSummary.value;
});
tdSummary.appendChild(inputSummary);
inputSummary.onchange();
const tdType = document.createElement('td');
tr.appendChild(tdType);
const selectType = document.createElement("select");
selectType.classList.add('form-select');
tdType.prepend(selectType);
const optionHoliday = document.createElement("option");
optionHoliday.text = "Holiday";
optionHoliday.value = "h";
optionHoliday.selected = holiday.type === "h";
selectType.add(optionHoliday);
const optionOther = document.createElement("option");
optionOther.text = "Other";
optionOther.value = "o";
optionOther.selected = holiday.type === "o";
selectType.add(optionOther);
selectType.onchange = (e => {
holiday.type = e.target.value;
});
const tdRepeat = document.createElement('td');
tr.appendChild(tdRepeat);
const selectRepeat = document.createElement("select");
selectRepeat.classList.add('form-select');
tdRepeat.prepend(selectRepeat);
const optionNever = document.createElement("option");
optionNever.text = "Never";
optionNever.selected = !holiday.repeat;
selectRepeat.add(optionNever);
const optionYearly = document.createElement("option");
optionYearly.text = "Yearly";
optionYearly.value = "y";
optionYearly.selected = holiday.repeat === "y";
selectRepeat.add(optionYearly);
selectRepeat.onchange = (e => {
holiday.repeat = e.target.value;
});
const tdAction = document.createElement('td');
tr.appendChild(tdAction);
const buttonDelete = document.createElement('button');
buttonDelete.classList.add('btn');
buttonDelete.classList.add('btn-action');
tdAction.prepend(buttonDelete);
const iconDelete = document.createElement('i');
iconDelete.classList.add('icon');
iconDelete.classList.add('icon-delete');
buttonDelete.appendChild(iconDelete);
buttonDelete.onclick = (e => {
holidays = holidays.filter(a => a !== holiday);
document.getElementById('events').removeChild(tr);
});
document.getElementById('events').appendChild(tr);
document.getElementById('upload').disabled = false;
}
function addHoliday() {
const holiday = {date: formatDate(new Date()), type: 'h'};
renderHoliday(holiday);
holidays.push(holiday);
render();
}
function getData() {
Util.showModal("Loading...");
Puck.write(`\x10(function() {
Bluetooth.print(JSON.stringify(require("Storage").list("calendar.days.json").sort()));
})()\n`, contents => {
const fileNames = JSON.parse(contents);
if (fileNames.length > 0) {
Util.readStorage('calendar.days.json',data=>{
holidays = JSON.parse(data || "[]") || [];
Util.hideModal();
render();
});
} else {
holidays = [];
Util.hideModal();
}
});
}
// Called when app starts
function onInit() {
getData();
}
</script>
</head>
<body>
<h4 class="float-left">Holidays</h4>
<div class="float-right">
<button class="btn" onclick="addHoliday();">
<i class="icon icon-plus"></i>
</button>
</div>
<table class="table table-scroll" style="clear:both;">
<thead>
<tr>
<th>Date</th>
<th>Holiday</th>
<th>Type</th>
<th>Repeat</th>
<th></th>
</tr>
</thead>
<tbody id="events">
</tbody>
</table>
<div class="divider"></div>
<div class="form-horizontal">
<div class="form-group">
<div class="col-5 col-xs-12">
<label class="form-label" for="fileinput">Add from iCalendar file</label>
</div>
<div class="col-7 col-xs-12">
<input id="fileinput" class="form-input" type="file" onchange="readFile(this)" accept=".ics,.ifb,.ical,.ifbf" multiple/>
</div>
</div>
</div>
<div class="divider"></div>
<button id="upload" class="btn btn-primary" onClick="upload()" disabled>Upload</button>
<button id="reload" class="btn" onClick="location.reload()">Reload</button>
</body>
</html>

View File

@ -1,7 +1,7 @@
{
"id": "calendar",
"name": "Calendar",
"version": "0.13",
"version": "0.14",
"description": "Simple calendar",
"icon": "calendar.png",
"screenshots": [{"url":"screenshot_calendar.png"}],
@ -9,10 +9,11 @@
"supports": ["BANGLEJS","BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"interface": "interface.html",
"storage": [
{"name":"calendar.app.js","url":"calendar.js"},
{"name":"calendar.settings.js","url":"settings.js"},
{"name":"calendar.img","url":"calendar-icon.js","evaluate":true}
],
"data": [{"name":"calendar.json"}]
"data": [{"name":"calendar.json"}, {"name":"calendar.days.json"}]
}

1
apps/clkinfom/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: First version

11
apps/clkinfom/README.md Normal file
View File

@ -0,0 +1,11 @@
# RAM Clock Info
![](app.png)
A clock info that displays the % memory used
## Screenshots
![](screenshot.png)
Written by: [Hugh Barney](https://github.com/hughbarney) For support and discussion please post in the [Bangle JS Forum](http://forum.espruino.com/microcosms/1424/)

BIN
apps/clkinfom/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 B

61
apps/clkinfom/clkinfo.js Normal file
View File

@ -0,0 +1,61 @@
(function () {
var timeout;
var debug = function(o) {
//console.log(o);
};
var clearTimer = function() {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
debug("timer cleared");
}
};
var queueRedraw = function() {
clearTimer();
timeout = setTimeout(function() {
timeout = undefined;
queueRedraw();
}, 60000);
info.items[0].emit("redraw");
debug("queued");
};
var img = function() {
return atob("GBgBAAAAAAAAAAAAB//gD//wH//4HgB4HAA4HAA4HAA4HDw4HDw4HDw4HDw4HAA4HAA4HAA4HgB4H//4D//wB//gAAAAAAAAAAAA");
};
var text = function() {
var val = process.memory(false);
return '' + Math.round(val.usage*100 / val.total) + '%';
};
var info = {
name: "Bangle",
items: [
{
name: "ram",
get: function () { return ({
img: img(),
text: text()
}); },
run : function() {
debug("run");
queueRedraw();
},
show: function () {
debug("show");
this.run();
},
hide: function() {
debug("hide");
clearTimer();
}
}
]
};
return info;
});

View File

@ -0,0 +1,15 @@
{
"id": "clkinfom",
"name": "RAM Clock Info",
"version":"0.01",
"description": "Clockinfo that displays % used memory",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clkinfo",
"tags": "clkinfo",
"supports" : ["BANGLEJS2"],
"readme":"README.md",
"storage": [
{"name":"ram.clkinfo.js","url":"clkinfo.js"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -5,7 +5,7 @@
"version": "0.08",
"description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch",
"icon": "icons8-cycling-48.png",
"tags": "outdoors,exercise,ble,bluetooth",
"tags": "outdoors,exercise,ble,bluetooth,bike,cycle,bicycle",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [

View File

@ -2,8 +2,6 @@
Logs health data to a file in a defined interval, and provides an app to view it
**BETA - requires firmware 2v11 or later**
## Usage
Once installed, health data is logged automatically.

View File

@ -217,7 +217,7 @@ exports.buzz = function(msgSrc) {
if (repeat===undefined) repeat = 4; // repeat may be zero
if (repeat)
{
exports.buzzTimeout = setInterval(() => require("buzz").pattern(pattern), repeat*1000);
exports.buzzInterval = setInterval(() => require("buzz").pattern(pattern), repeat*1000);
let vibrateTimeout = msgSettings.vibrateTimeout;
if (vibrateTimeout===undefined) vibrateTimeout = 60;
if (vibrateTimeout && !exports.stopTimeout) exports.stopTimeout = setTimeout(exports.stopBuzz, vibrateTimeout*1000);
@ -228,8 +228,8 @@ exports.buzz = function(msgSrc) {
* Stop buzzing
*/
exports.stopBuzz = function() {
if (exports.buzzTimeout) clearTimeout(exports.buzzTimeout);
delete exports.buzzTimeout;
if (exports.buzzInterval) clearInterval(exports.buzzInterval);
delete exports.buzzInterval;
if (exports.stopTimeout) clearTimeout(exports.stopTimeout);
delete exports.stopTimeout;
};

View File

@ -0,0 +1,2 @@
0.01: New App!
0.02: Show the date inside the widget bar

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEw4UA///8H55lCsHZHmEKgEVqoLIBQNVqgLGh4KBAANQBAUQBY1VoApBB4QLFDYoL/Bf4L/BbLrBBZAKBgEBBY0KAQIABgoLCCYQLEgEVBQYLGAAoL/Bf4LPAFw"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

89
apps/patriotclk/app.js Normal file
View File

@ -0,0 +1,89 @@
Graphics.prototype.setFontAudiowide = function() {
// Actual height 33 (36 - 4)
return this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('AB/wAgcB/AFVgFgHbkYAok4AogvEgYFEg4FEj4FEn4FE//gKQf/4AcD/4QDh/8Djf+DhN/T4YcFgYcKh4cEh68Eh4cDAoOAAocORYkMf1JxBIYcf/6PDn//MIYEB/5KBOIIABKwIFFO4V/UQMHEIMfFQMHAQP3AQJ3BDIKABh/ggf7ApHAg/5AonxAocPAokf8IFE4IFDn4FEv+BAokBAof/AofB/wFE/gFD4YFE4/4AohgBAoXPAonvMAIFD4AFCVgIFBQYX3wCGCR4T+CTYqtLX4rLC/zXIcYoAQQYIFiJoR9CArgAlToIpDRQIFDSwI7C4CiBApN/Apb1D4F+Av4Fd8H+Aof/AoaTB/gFIgaBBAoSrB+AFCgF/8AFDAESP/Av3wv0HZYYABYoYAB+AFGZYIAB8DLCAAPAZYQFBZUhHC/gFE/wFaAAN+Av4Fqv53EboYFdAFIvB4EBGofwAon4Aon8ApX+AofAAot+Av4Fev8DAojFDAo0/S4IFGAAMf//gV4mAAoUD/zYgFwP8AoRGB/4FCAgI1CgIFC4A5BAoRHBg4FCKYMH/l+n5fC+F+g5rC8F+PoYFFZf7XVw7XNAALXNTYLXCVoYAQF4IFZjAFEnAFELIZCBAojRDAoMfAol/AohrCAoJfBNYIFBNYOAAoUf/xBDv/8AoXBRAcP4aCDh/PDgSNCDgQFCHIIFDUoafFAoJ3EGYQFCDgYFBXgZuBGYQAba4pDEhzvE/4ABKoMBAogbBAAJKBg4EBw4FEX4Z9BgIFC8AFE4F+Av5HFKYhfFAoRxCO4qDFgF/AokATYgfCZwcD/zTdAAV/Z4RBCHIZNBJYI5D/gFFOJEP+DF/a7N+ZYQFG+F+g7XFRYIFFbobLBboajCAoTRCcYTiEUQYAdgYCBsACBMwJlCAqUHJYLxDAAMgHSQ'))),
46,
atob("CiAsESQjJSQkHyQkDA=="),
48|65536
);
};
{
let img = require("Storage").read("patriotclk.bg.img");
let options = require("Storage").readJSON("patriotclk.opts",1)||{};
// timeout used to update every minute
let drawTimeout, widgetTimeout;
// draw everything
let draw = function() {
var x = g.getWidth()/2;
var y;
if (options.bottomText)
y = g.getHeight() - 24;
else
y = 4+(g.getHeight())/2; // middle
g.reset();
g.drawImage(img,0,0);
// work out locale-friendly date/time
var date = new Date();
var timeStr = require("locale").time(date,1);
var dateStr = require("locale").date(date);
// draw time
g.setFontAlign(0,0).setFont("Audiowide");
// draw a shadow by shifting left/right/up/down
g.drawString(timeStr,x-6,y);
g.drawString(timeStr,x+6,y);
g.drawString(timeStr,x,y-6);
g.drawString(timeStr,x,y+6);
g.drawString(timeStr,x-4,y+4);
g.drawString(timeStr,x+4,y+4);
g.drawString(timeStr,x-4,y-4);
g.drawString(timeStr,x+4,y-4);
// finally draw in the foreground color
g.setColor(g.theme.bg).drawString(timeStr,x,y);
// draw date
//y += 35;
//g.setFontAlign(0,0).setFont("6x8").g.setColor(g.theme.fg);
//g.drawString(dateStr,x,y);
// queue draw in one minute
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
};
// Show launcher when middle button pressed
Bangle.setUI({mode:"clock", remove:function() { //f ree memory
if (drawTimeout) clearTimeout(drawTimeout);
if (widgetTimeout) clearTimeout(widgetTimeout);
require("widget_utils").show();
var e = WIDGETS["patriot"];
g.reset().clearRect(e.x,e.y,e.x+63,e.y+23);
delete WIDGETS["patriot"];
delete Graphics.prototype.setFontAudiowide;
}});
// Load widgets (make them swipeable)
Bangle.loadWidgets();
WIDGETS["patriot"] = {
area:"tl",
width: 64, // how wide is the widget? You can change this and call Bangle.drawWidgets() to re-layout
draw : function(e) {
g.reset().clearRect(e.x,e.y,e.x+63,e.y+23);
var d = new Date();
g.setFont("6x8").setFontAlign(-1,0).drawString(require("locale").dow(d,0), e.x+2, e.y+8);
g.setFont("6x8").setFontAlign(-1,0).drawString(require("locale").date(d).trim(), e.x+2, e.y+16);
widgetTimeout = setTimeout(function() { // redraw every hour (it's just easier that working out timezones)
widgetTimeout = undefined;
WIDGETS["patriot"].draw(WIDGETS["patriot"]);
}, 3600000 - (Date.now() % 3600000));
}
};
require("widget_utils").swipeOn();
// Clear the screen once, at startup
g.clear();
// draw immediately at first, queue update
draw();
}

BIN
apps/patriotclk/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

143
apps/patriotclk/custom.html Normal file
View File

@ -0,0 +1,143 @@
<html>
<head>
<link rel="stylesheet" href="../../css/spectre.min.css">
<style>
.flag {
width : 100px;
cursor: pointer;
}
#preview {
width : 176px;
height: 176px;
}
</style>
</head>
<body>
<p>Please choose your country's flag:</p>
<div id="flaglist"></div>
<p>Is your flag not here? Please <a href="https://github.com/espruino/BangleApps/tree/master/apps/patriotclk/img" target="_blank">add it here</a>! </p>
<div style="float:right">Preview:<br/><canvas width="176" height="176" id="preview"></canvas></div>
<div class="form-group">
<label class="form-switch">
<input type="checkbox" id="box_zoom">
<i class="form-icon"></i> Zoom
</label>
<label class="form-switch">
<input type="checkbox" id="box_textbottom">
<i class="form-icon"></i> Time at bottom
</label>
</div>
<p>Click <button id="upload" class="btn btn-primary disabled" style>Upload</button></p>
<img id="preview_overlay" src="app-preview.png" style="display:none">
<img id="preview_overlay_btm" src="app-preview-btm.png" style="display:none">
<script src="../../core/lib/customize.js"></script>
<script src="../../webtools/imageconverter.js"></script>
<script>
var FLAGS = `
icons8-australia-480.png
icons8-austria-480.png
icons8-belgium-480.png
icons8-brazil-480.png
icons8-canada-480.png
icons8-china-480.png
icons8-denmark-480.png
icons8-england-480.png
icons8-flag-of-europe-480.png
icons8-france-480.png
icons8-germany-480.png
icons8-great-britain-480.png
icons8-greece-480.png
icons8-hungary-480.png
icons8-italy-480.png
icons8-lgbt-flag-480.png
icons8-netherlands-480.png
icons8-new-zealand-480.png
icons8-norway-480.png
icons8-scotland-480.png
icons8-spain-480.png
icons8-sweden-480.png
icons8-switzerland-480.png
icons8-ukraine-480.png
icons8-usa-480.png
icons8-wales-480.png
`.trim().split("\n").map(s=>s.trim());
// If we can't map a flag direct to 3 bit color, enable dithering
var DITHERED_FLAGS = `
icons8-lgbt-flag-480.png
icons8-ukraine-480.png
`.trim().split("\n").map(s=>s.trim());
var selectedImage;
var bgImageData;
var clockOptions = {
bottomText : false
};
document.getElementById("flaglist").innerHTML =
FLAGS.map(f => `<img class="flag" src="img/${f}" data-file="${f}"/>`).join("\n");
var elements = document.querySelectorAll(".flag");
for (var i=0;i<elements.length;i++)
elements[i].addEventListener("click", function(e) {
selectedImage = e.target;
drawPreview();
document.getElementById("upload").classList.remove("disabled")
});
function drawPreview() {
if (!selectedImage) return;
var imgFile = selectedImage.getAttribute("data-file");
var zoom = document.getElementById("box_zoom").checked;
clockOptions.bottomText = document.getElementById("box_textbottom").checked;
const canvas = document.getElementById("preview");
canvas.width = 176; // setting size clears canvas
canvas.height = 176;
const ctx = canvas.getContext("2d");
var y = 0;
if (clockOptions.bottomText)
y = -28;
if (zoom)
ctx.drawImage(selectedImage, 90, 90, 300, 300, 0, y, 176, 176);
else
ctx.drawImage(selectedImage, 20, 20, 440, 440, 0, y, 176, 176);
var options = {
mode:"3bit",
output:"raw",
compression:false,
updateCanvas:true,
transparent:false,
contrast:128
};
if (DITHERED_FLAGS.includes(imgFile))
options.diffusion = "bayer2";
bgImageData = imageconverter.canvastoString(canvas, options);
ctx.drawImage(document.getElementById(clockOptions.bottomText?"preview_overlay_btm":"preview_overlay"),0,0);
}
// If options changed
document.getElementById("box_zoom").addEventListener("click", function() {
drawPreview();
});
document.getElementById("box_textbottom").addEventListener("click", function() {
drawPreview();
});
// When the 'upload' button is clicked...
document.getElementById("upload").addEventListener("click", function() {
// send finished app (in addition to contents of metadata.json)
if (bgImageData) sendCustomizedApp({
storage:[
{name:"patriotclk.bg.img", content:bgImageData},
{name:"patriotclk.opts", content:JSON.stringify(clockOptions)}, // we don't save as .json or we'd have a sanitycheck warning
]
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,9 @@
Flags
------
These flags come from https://icons8.com/icon/set/flags/color and are 480x480px
If you want to add your own flag ensure it's in the same style and then also list the image file in custom.html in the root directory.
If your flag is listed in https://icons8.com/icon/set/flags/color and you can't download it in the right size, please file an issue and we'll download it with our account.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -0,0 +1,18 @@
{ "id": "patriotclk",
"name": "Patriotic Clock",
"shortName":"Patriot",
"version":"0.02",
"description": "Show your Patriotism with your Country's flag as a clock face (configurable). Swipe down to show widgets and date.",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
"tags": "clock,flag",
"supports" : ["BANGLEJS2"],
"custom": "custom.html",
"storage": [
{"name":"patriotclk.app.js","url":"app.js"},
{"name":"patriotclk.bg.img"},
{"name":"patriotclk.opts"},
{"name":"patriotclk.img","url":"app-icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Trim old entries from the popcon app cache

View File

@ -3,6 +3,6 @@ Popcon
Display apps sorted by regular use. No config - install the app and all your launchers will sort apps by most popular, based off launch counts within the last month, and then sort them by the most recently launched app.
:warning: Warning: this app overrides [`Storage.readJSON`], so may slow down your watch when using apps that perform I/O.
⚠️ Warning: this app overrides [`Storage.readJSON`], so may slow down your watch when using apps that perform I/O.
[`Storage.readJSON`]: https://www.espruino.com/ReferenceBANGLEJS2#l_Storage_readJSON

View File

@ -1,6 +1,7 @@
{
var oldRead_1 = require("Storage").readJSON;
var monthAgo_1 = Date.now() - 1000 * 86400 * 28;
var oneMonth_1 = 1000 * 86400 * 28;
var monthAgo_1 = Date.now() - oneMonth_1;
var cache_1;
var ensureCache_1 = function () {
if (!cache_1) {
@ -10,8 +11,20 @@
}
return cache_1;
};
var saveCache_1 = function (orderChanged) {
require("Storage").writeJSON("popcon.cache.json", cache_1);
var trimCache_1 = function (cache) {
var threeMonthsBack = Date.now() - oneMonth_1 * 3;
var del = [];
for (var k in cache)
if (cache[k].last < threeMonthsBack)
del.push(k);
for (var _i = 0, del_1 = del; _i < del_1.length; _i++) {
var k = del_1[_i];
delete cache[k];
}
};
var saveCache_1 = function (cache, orderChanged) {
trimCache_1(cache);
require("Storage").writeJSON("popcon.cache.json", cache);
if (orderChanged) {
var info = oldRead_1("popcon.info", true);
info.cacheBuster = !info.cacheBuster;
@ -74,7 +87,7 @@
ent.pop++;
ent.last = Date.now();
var orderChanged = sortCache_1();
saveCache_1(orderChanged);
saveCache_1(cache_3, orderChanged);
}
return oldLoad_1(src);
};

View File

@ -1,9 +1,6 @@
{
type Timestamp = number;
const oldRead = require("Storage").readJSON;
const monthAgo = Date.now() - 1000 * 86400 * 28;
let cache: undefined | {
type Cache = {
[key: string]: {
sortorder: number,
pop: number, // amount of launches
@ -11,6 +8,11 @@ let cache: undefined | {
}
};
const oldRead = require("Storage").readJSON;
const oneMonth = 1000 * 86400 * 28;
const monthAgo = Date.now() - oneMonth;
let cache: undefined | Cache;
const ensureCache = (): NonNull<typeof cache> => {
if(!cache){
cache = oldRead("popcon.cache.json", true);
@ -20,7 +22,19 @@ const ensureCache = (): NonNull<typeof cache> => {
return cache;
};
const saveCache = (orderChanged: boolean) => {
const trimCache = (cache: Cache) => {
const threeMonthsBack = Date.now() - oneMonth * 3;
const del = [];
for(const k in cache)
if(cache[k]!.last < threeMonthsBack)
del.push(k);
for(const k of del)
delete cache[k];
};
const saveCache = (cache: Cache, orderChanged: boolean) => {
trimCache(cache);
require("Storage").writeJSON("popcon.cache.json", cache);
if(orderChanged){
// ensure launchers reload their caches:
@ -94,7 +108,7 @@ global.load = (src: string) => {
ent.pop++;
ent.last = Date.now();
const orderChanged = sortCache();
saveCache(orderChanged);
saveCache(cache, orderChanged);
}
return oldLoad(src);

View File

@ -2,7 +2,7 @@
"id": "popconlaunch",
"name": "Popcon Launcher",
"shortName": "Popcon",
"version": "0.01",
"version": "0.02",
"description": "Launcher modification - your launchers will display your favourite (popular) apps first. Overrides `readJSON`, may slow down your watch",
"readme": "README.md",
"icon": "app.png",

View File

@ -9,4 +9,4 @@
0.07: Convert Yes/No On/Off in settings to checkboxes
0.08: Fix the wrapping of intervals/timeouts with parameters
Fix the widget drawing if widgets are hidden and Bangle.setLCDBrightness is called
0.09: Cache the app-launch info
0.09: Accidental version bump

View File

@ -1 +1,2 @@
0.01: Initial release
0.02: Cache the app-launch info

View File

@ -2,7 +2,7 @@
"id": "ratchet_launch",
"name": "Ratchet Launcher",
"shortName": "Ratchet",
"version": "0.01",
"version": "0.02",
"description": "Launcher with discrete scrolling for quicker app selection",
"icon": "app.png",
"type": "launch",

View File

@ -15,10 +15,11 @@ You can record
* **Time** The current time
* **GPS** GPS Latitude, Longitude and Altitude
* **Steps** Steps counted by the step counter
* **HR** Heart rate and confidence
* **BAT** Battery percentage and voltage
* **Core** CoreTemp body temperature
* **Steps** Steps counted by the step counter
* **Baro** (Bangle.js 2) Using the built-in barometer to record Temperature, Pressure and Altitude
* **Core** CoreTemp body temperature *if* you have a CoreTemp device and the https://banglejs.com/apps/?id=coretemp app installed
You can then start/stop recording from the Recorder app itself (and as long as widgets are
enabled in the app you're using, you can move to another app and continue recording).

View File

@ -3,7 +3,7 @@
<link rel="stylesheet" href="../../css/spectre.min.css">
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
<script src="../../core/lib/interface.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ical.js/0.0.3/ical.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ical.js/1.5.0/ical.min.js"></script>
<script>
let dataElement = document.getElementById("data");
let alarms;
@ -17,7 +17,7 @@ function readFile(input) {
const reader = new FileReader();
reader.addEventListener("load", () => {
const jCalData = ICAL.parse(reader.result);
const comp = new ICAL.Component(jCalData[1]);
const comp = new ICAL.Component(jCalData);
// Fetch the VEVENT part
comp.getAllSubcomponents('vevent').forEach(vevent => {
event = new ICAL.Event(vevent);
@ -50,13 +50,17 @@ function dateFromAlarm(alarm) {
return new Date(date.getTime() + alarm.t);
}
function formatDate(d) {
return d.getFullYear() + "-" + (d.getMonth() + 1).toString().padStart(2, '0') + "-" + d.getDate().toString().padStart(2, '0');
}
function getAlarmDefaults() {
const date = new Date();
return {
on: true,
t: dateToMsSinceMidnight(date),
dow: 127,
date: date.toISOString().substring(0,10),
date: formatDate(date),
last: 0,
rp: "defaultRepeat" in schedSettings ? schedSettings.defaultRepeat : false,
vibrate: "defaultAlarmPattern" in schedSettings ? schedSettings.defaultAlarmPattern : "::",
@ -72,7 +76,7 @@ function eventToAlarm(event, offsetMs) {
id: event.uid,
msg: event.summary,
t: dateToMsSinceMidnight(date),
date: date.toISOString().substring(0,10),
date: formatDate(date),
data: {end: event.endDate.toJSDate().toISOString()}
}};
if (offsetMs) { // Alarm time is not real event time, so do a backup
@ -105,7 +109,7 @@ function renderAlarm(alarm, exists) {
inputTime.onchange = (e => {
const date = new Date(inputTime.value);
alarm.t = dateToMsSinceMidnight(date);
alarm.date = date.toISOString().substring(0,10);
alarm.date = formatDate(date);
});
tdTime.appendChild(inputTime);

View File

@ -1 +1,2 @@
0.01: New App!
0.02: New 'Settings Menu' to choose your favorite color and switch between light or dark themes

25
apps/shadowclk/README.md Normal file
View File

@ -0,0 +1,25 @@
# Shadow Clock
Shadow Clock uses the "Londrina" font in a user selectable color and surrounds it in the "Londrina Shadow" font to create a visually appealing way to show the time in a clear, easily readable manner. On the Bangle.js 2, the time can be read easily even if the screen is locked and unlit.
## Usage
* Install Shadow Clock through the Bangle.js app loader.
* Configure it through the default Bangle.js configuration mechanism
(Settings app, "Apps" menu, "Shadow Clock" submenu).
* If you like it, make it your default watch face
(Settings app, "System" menu, "Clock" submenu, select "Shadow Clock").
## Configuration
Anton Clock is configured by the standard settings mechanism of Bangle.js's operating system:
Open the `Settings` app, then the `Apps` submenu and below it the `Shadow Clock` menu.
You configure Shadow Clock by selecting a `Light` or `Dark` system wide theme and then selecting the `Color` of the clock numbers.
## Compatibility
Shadow Clock should be fully compatible with with Bangle.js 1 and Bangle.js 2. However, it was built and tested with Bangle.js 2
## Creator
[stweedo](https://github.com/stweedo)

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mEwhHXAH4A/AH4A/AH4A/AH4A/AH4A/AFGsAAQOUA4OBDBYfHo8jAAOBByOssgICkdkF6AWEF5IOHxAHDAAVALx4VEF5AOHA4oAD0ovNoEjo6BCF5AOHF4ccp9dAoVdFxh2CioUCa44OIwIFCirwBjgFBqyNPqxJCEAOIKwOIBxYJChwfBwIABJQ7dHnIkCEAsVBxovDdiMcVYIgHFQgOJR4WIxBdMCoccshUCkdkRYJ6DBxIvDAAlkF5wWLBxQKJOAIvTroOOBQkcmU5aoYAKhwACjiqDqwOOTQQqDGwQvMAAawCcoOsAAQOL642DBAI0EAB1AC4QfBJIU5BxQ2DAAp5FF6ZJFF4xgFAAMOOwyPKjkcCgWsAoMyBxQAB1k5BAMcZAQuPDIS6EYBAIRAHKHCAIhPLAoWswIMEL6OBDIgYGHA4qCCwIKBDIgvPDggnIAw7lBF4J4KR5gVJKgZDDIgQCDMITvXXA47EHxD2aFQhOCBAY8DRiiWLD4aEBMYzuCLzgvEEQK4BFAQCDB4TACF7QA/AH4A/AH4A/AAQA=="))
require("heatshrink").decompress(atob("mEwxH+64Afq17AAlWq2sBYOtB4Urq2BFzeBwONAAeyHAQvCrPX1g3BlYvb02BjwADF4QmBwdZF4MrleBgAvlgAwBgqQBlcAF72HF46PBwaPDwK/dVoLpBAAwMBweDFTYA/AH4A/AH4A/AF2lAAOsBRIAF6AMDp9Wp+lFyNWkYABitWFwoKCAAsHBgMyBAkVwIuO6EVCod6F50A6+BBI0VMRxGEF6BfBIwZqHABRGCo5EIF4cOgEHAAUA0scBYQDDCAIvMhx7CCYTfDAYIvDK4McNoaODh1PPgcO1jsNiskOYQTBh0cjlW1iDHfwJfDjmeoDKEABSlCgAlCF4QJGcgwPEfQ5eMFQIvDRYIfClesioADEgcrSAguEwJeNh1AAgQkBwOlkkkQoOBvQACIgRAB685F4wJBF5oAGp4UJ1i6CeQK/Dg4ECZYQvTComsnNdrp+B0rADlaKD1l6X52BgAACJIgvEFIcO0sOF4cAAgeISgYvLMg8HKoOBAAIvEAAsOL4bwGFxyuDg/QGoYZBT5AKBXQYvVCYUH1gvFqwjGTwWBGA0AOwIAN6EOhytBAwIFBh1PAoN6g8VskVmUAZoelBQVABQIuPMAS6DfYWBMgI9CwMrq2lDA2Bq1PFqLpCHBAWKMIJGBAgRDEABiKBlYTDAYcA1gKBqwgFlYMBLoIaBCIKZDABh+BDIQVBC4WsF4YOBGAmsBQSgCAIIvQFYIiBKwReDFQI3DEIlWFIIsBCwIABF6JhClZhBZQo8BAYIOBIoh0CBIJ5DAH4AEPYKBDWwSGCAoKIDAoIwcEwJ/CRYICCFoI8BQ4YKBMDhWBEwIjBYQcAAYQKBZIRfdAIJVCQgLkDAYJjCcX4A/AH4A/AFwA=="))

View File

@ -1,63 +1,95 @@
// Clock with large colored digits using the "Londrina" font and a slightly larger "Londrina Shadow" font on top
Graphics.prototype.setFontLondrinaSolid = function() {
// Actual height 59 (64 - 6)
return this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('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'))),
46,
atob("DyEqHigoJikpJygqEQ=="),
81|65536
);
};
Graphics.prototype.setFontLondrinaShadow = function() {
// Actual height 63 (67 - 5)
return this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('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'))),
46,
atob("DyEqHigoJikpJygqEQ=="),
81|65536
);
};
(function() {
let drawTimeout;
// Actually draw the watch face
function draw() {
const x = g.getWidth() / 2;
const y = g.getHeight() / 2;
g.reset().clearRect(Bangle.appRect);
const date = new Date();
var hour = String(date.getHours()).padStart(2, '0');
if (hour[0] === '0') hour = hour[1];
var minutes = String(date.getMinutes()).padStart(2, '0');
const timeStr = hour + ':' + minutes;
g.setFontAlign(0, 0).setFont("LondrinaSolid").setColor(0, 1, 1).drawString(timeStr, x - 1, y);
g.reset().setFontAlign(0, 0).setFont("LondrinaShadow").drawString(timeStr, x - 1, y);
const locale = require("locale");
const dateStr = locale.date(date, 0).toUpperCase() + "\n" +
locale.dow(date, 0).toUpperCase();
g.setFontAlign(0, 0).setFont("6x8", 2).drawString(dateStr, x, y + 48);
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(() => {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
Bangle.setUI({
mode: "clock",
remove: function() {
// Actual height 59 (64 - 6)
return this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('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'))),
46,
atob("DyEqHigoJikpJygqEQ=="),
81 | 65536
);
};
Graphics.prototype.setFontLondrinaShadow = function() {
// Actual height 63 (67 - 5)
return this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('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'))),
46,
atob("DyEqHigoJikpJygqEQ=="),
81 | 65536
);
};
Graphics.prototype.setFontDotGothic16 = function() {
// Actual height 20 (19 - 0)
return this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('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'))),
32,
atob("CgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoA"),
20|65536
);
};
let storage = require('Storage');
var settings = Object.assign({
// default values
color: "#0ff",
theme: "light",
}, storage.readJSON("shadowclk.json", true) || {});
(function() {
let drawTimeout;
function draw() {
var x = g.getWidth() / 2;
var y = g.getHeight() / 2;
g.reset().clearRect(Bangle.appRect);
var date = new Date();
var hour = String(date.getHours()).padStart(2, '0');
if (hour[0] === '0') hour = hour[1];
var minutes = String(date.getMinutes()).padStart(2, '0');
var timeStr = hour + ':' + minutes;
var color = settings.color;
g.setFontAlign(0, 0).setFont("LondrinaSolid").setColor(color).drawString(timeStr, x - 1, y);
g.reset().setFontAlign(0, 0).setFont("LondrinaShadow").drawString(timeStr, x - 1, y);
var locale = require("locale");
var dayOfMonth = date.getDate();
var month = locale.month(date, 1).slice(0, 1).toUpperCase() + locale.month(date, 1).slice(1).toLowerCase();
var year = date.getFullYear();
var dayOfMonthStr = dayOfMonth.toString();
if (dayOfMonth === 1 || dayOfMonth === 21 || dayOfMonth === 31) {
dayOfMonthStr += "st";
} else if (dayOfMonth === 2 || dayOfMonth === 22) {
dayOfMonthStr += "nd";
} else if (dayOfMonth === 3 || dayOfMonth === 23) {
dayOfMonthStr += "rd";
} else {
dayOfMonthStr += "th";
}
var dayOfWeek = locale.dow(date, 0).slice(0, 1).toUpperCase() + locale.dow(date, 0).slice(1).toLowerCase();
var dateStr = month + " " + dayOfMonthStr + ", " + year + "\n" + dayOfWeek;
g.setFontAlign(0, 0).setFont("DotGothic16").drawString(dateStr, x, y + 48);
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
drawTimeout = setTimeout(() => {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
});
Bangle.loadWidgets();
draw();
setTimeout(Bangle.drawWidgets, 0);
})();
Bangle.setUI({
mode: "clock",
remove: function() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
delete Graphics.prototype.setFontLondrinaSolid;
delete Graphics.prototype.setFontLondrinaShadow;
delete Graphics.prototype.setFontDotGothic16;
}
});
Bangle.loadWidgets();
draw();
setTimeout(Bangle.drawWidgets, 0);
})();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
apps/shadowclk/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 446 B

View File

@ -0,0 +1,370 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="../../css/spectre.min.css">
<link href="https://fonts.googleapis.com/css2?family=Londrina+Solid&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Londrina+Shadow&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=DotGothic16&display=swap" rel="stylesheet">
<title>3-Bit Color Picker</title>
<style>
.main-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
}
.color-button {
width: 30px;
height: 30px;
border: 1px solid black;
margin: 5px;
cursor: pointer;
}
.color-0 {
background: #000
}
.color-1 {
background: #f00
}
.color-2 {
background: #0f0
}
.color-3 {
background: #ff0
}
.color-4 {
background: #00f
}
.color-5 {
background: #f0f
}
.color-6 {
background: #0ff
}
.color-7 {
background: #fff
}
#preview-box {
width: 176px;
height: 176px;
border: 1px solid black;
margin-top: 20px;
display: flex;
align-items: center;
justify-content: center;
background: white;
}
#preview-canvas {
display: block;
}
#toggle-bg {
margin-top: 20px;
}
#upload {
margin-top: 20px;
}
#message-container {
height: 40px;
/* adjust the height based on your desired fixed height */
margin-top: 20px;
text-align: center;
}
</style>
</head>
<body>
<script src="../../core/lib/interface.js"></script>
<div class="main-container">
<h1>3-Bit Color Picker</h1>
<div id="color-buttons-container"></div>
<button id="toggle-bg" class="btn btn-primary" onclick="toggleBackground()">Light/Dark</button>
<div id="preview-box">
<canvas id="preview-canvas" width="176" height="176"></canvas>
</div>
<button id="upload" class="btn btn-primary">Upload</button>
<div id="message-container">
<div id="message"></div>
</div>
</div>
<script>
const messageDiv = document.getElementById('message');
let colors = ['#000', '#f00', '#0f0', '#ff0', '#00f', '#f0f', '#0ff', '#fff'];
let colorButtonsContainer = document.getElementById('color-buttons-container');
colors.forEach((color, i) => {
let button = document.createElement('button');
button.className = `color-button color-${i}`;
button.dataset.color = color;
colorButtonsContainer.appendChild(button);
});
document.querySelectorAll(".color-button").forEach(button => {
button.addEventListener("click", () => {
selectedColor = button.dataset.color;
drawText(selectedColor);
});
});
function formatTime(date) {
let hours = date.getHours();
let minutes = date.getMinutes();
let formattedHours = hours < 10 ? `${hours}` : `${hours}`;
let formattedMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
return `${formattedHours}:${formattedMinutes}`;
}
function getCurrentDate() {
let months = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
let suffixes = ["st", "nd", "rd", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th", "th", "st"];
let date = new Date();
let month = months[date.getMonth()];
let day = date.getDate();
let suffix = suffixes[day - 1];
let year = date.getFullYear();
return `${month} ${day}${suffix}, ${year}`;
}
function getCurrentDay() {
let days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
let date = new Date();
return days[date.getDay()];
}
function toggleBackground() {
isDarkBg = !isDarkBg; // Toggle the background state
let previewBox = document.getElementById("preview-box");
previewBox.style.backgroundColor = isDarkBg ? "black" : "white";
drawText(selectedColor); // Redraw the text with updated color
}
function drawText(color) {
let canvas = document.getElementById("preview-canvas");
let ctx = canvas.getContext("2d");
let previewBox = document.getElementById("preview-box");
previewBox.style.backgroundColor = isDarkBg ? "black" : "white";
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Set the base font and selected color
ctx.font = "81px Londrina Solid";
ctx.fillStyle = color;
// Get the current local system time
let currentTime = formatTime(new Date());
// Measure the text width and calculate the horizontal position
let timeWidth = ctx.measureText(currentTime).width;
let xPos = (canvas.width - timeWidth) / 2;
// Measure the text height based on the font size and calculate the vertical position
let timeHeight = ctx.measureText('M').actualBoundingBoxAscent + ctx.measureText('M').actualBoundingBoxDescent;
let yPos = (canvas.height + timeHeight) / 2;
// Draw the time
ctx.fillText(currentTime, xPos, yPos);
// Set the outline font and color
ctx.font = "81px Londrina Shadow";
// Set the text color based on the background state
if (isDarkBg) {
ctx.fillStyle = "#fff";
} else {
ctx.fillStyle = "#000";
}
// Draw the time again
ctx.fillText(currentTime, xPos, yPos);
// Set the font for the date
ctx.font = "19px DotGothic16";
// Get the current date
let currentDate = getCurrentDate();
// Measure the date width and calculate the horizontal position
let dateWidth = ctx.measureText(currentDate).width;
xPos = (canvas.width - dateWidth) / 2;
// Draw the date
yPos += 20;
ctx.fillText(currentDate, xPos, yPos);
// Get the current day of the week
let currentDay = getCurrentDay();
// Measure the day width and calculate the horizontal position
let dayWidth = ctx.measureText(currentDay).width;
xPos = (canvas.width - dayWidth) / 2;
// Draw the day of the week
ctx.fillText(currentDay, xPos, yPos + 20);
}
function hexToDec(hex) {
if (hex.length === 4) {
hex = `#${hex[1]}${hex[1]}${hex[2]}${hex[2]}${hex[3]}${hex[3]}`;
}
let r = parseInt(hex.slice(1, 3), 16) >> 3;
let g = parseInt(hex.slice(3, 5), 16) >> 2;
let b = parseInt(hex.slice(5, 7), 16) >> 3;
return (r << 11) | (g << 5) | b;
}
// Colors from 'Light BW' and 'Dark BW' themes
function createThemeColors(isDarkBg) {
return isDarkBg ? {
fg: hexToDec("#fff"),
bg: hexToDec("#000"),
fg2: hexToDec("#fff"),
bg2: hexToDec("#004"),
fgH: hexToDec("#fff"),
bgH: hexToDec("#00f"),
dark: true
} : {
fg: hexToDec("#000"),
bg: hexToDec("#fff"),
fg2: hexToDec("#000"),
bg2: hexToDec("#cff"),
fgH: hexToDec("#000"),
bgH: hexToDec("#0ff"),
dark: false
};
}
function saveThemeToSettings(theme) {
Puck.eval('require("Storage").readJSON("setting.json", true)', (data) => {
if (data) {
// Ensure that data.theme exists
if (!data.theme) {
data.theme = {};
}
// Save all theme values
for (let key in theme) {
data.theme[key] = theme[key];
}
data.clock = "shadowclk.app.js"; // Set Shadow Clock as default
Puck.write(`require("Storage").write("setting.json", ${JSON.stringify(data)});\n`, (result) => {
console.log('Theme saved:', result);
});
}
});
}
// Load Shadow Clock color and theme setting
let selectedColor = "#0ff";
let isDarkBg = false;
function loadSettings(callback) {
// Set a timeout for loading the settings
const timeout = new Promise((_, reject) =>
setTimeout(() => reject(new Error("Timeout occurred")), 500)
);
const loadSettingsFromStorage = new Promise((resolve) => {
Puck.eval('require("Storage").readJSON("shadowclk.json", true)', (data) => {
if (data) {
// Apply color and theme from JSON
const { color, theme } = data;
selectedColor = color;
isDarkBg = theme === "dark";
displayMessage("Previous settings loaded.", 3000);
} else {
// Use default values if there is no data for color and theme
selectedColor = "#0ff";
isDarkBg = false;
}
resolve();
});
});
Promise.race([loadSettingsFromStorage, timeout])
.then(() => callback())
.catch((error) => {
console.error(error);
// Use default values in case of a timeout or error
selectedColor = "#0ff";
isDarkBg = false;
callback();
});
}
function updateTime() {
setInterval(() => {
drawText(selectedColor);
}, 1000);
}
function displayMessage(text, timeout) {
// Remove any existing message
while (messageDiv.firstChild) {
messageDiv.removeChild(messageDiv.firstChild);
}
// Create a new message element
const message = document.createElement('p');
message.innerHTML = text; // Use innerHTML instead of textContent
message.style.fontSize = '24px';
messageDiv.appendChild(message);
// Remove the message element after the timeout
setTimeout(() => {
messageDiv.removeChild(message);
}, timeout);
}
document.getElementById("upload").addEventListener("click", function () {
// Save theme settings to Bangle.js
let themeColors = createThemeColors(isDarkBg);
saveThemeToSettings(themeColors);
// Save Shadow Clock color setting
let data = {
color: selectedColor,
theme: isDarkBg ? "dark" : "light"
};
Puck.write(`require("Storage").write("shadowclk.json", ${JSON.stringify(data)});\n`, (result) => {
console.log('Color saved:', result);
});
// Display the message using displayMessage function
displayMessage('Configuration sent...<br>Hold button on Bangle.js', 5000);
});
function loadFonts() {
return Promise.all([
document.fonts.load('81px Londrina Solid'),
document.fonts.load('81px Londrina Shadow'),
document.fonts.load('19px DotGothic16')
]);
}
async function init() {
await loadFonts(); // Load fonts before drawing for the first time
loadSettings(updateTime); // Pass updateTime as the callback function to loadSettings
}
init();
</script>
</body>
</html>

View File

@ -1,16 +1,35 @@
{
"id": "shadowclk",
"name": "Shadow Clock",
"version": "0.01",
"description": "A simple clock using the Londrina font with color and a shadowed outline. Based on the Anton Clock.",
"version": "0.02",
"description": "A simple clock using the Londrina font in color with a shadowed outline. Based on the Anton Clock.",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"},{"url":"screenshot-1.png"}],
"screenshots": [{
"url": "screenshot.png"
}, {
"url": "screenshot-1.png"
}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS","BANGLEJS2"],
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"interface": "interface.html",
"allow_emulator": true,
"storage": [
{"name":"shadowclk.app.js","url":"app.js"},
{"name":"shadowclk.img","url":"app-icon.js","evaluate":true}
]
}
"storage": [{
"name": "shadowclk.app.js",
"url": "app.js"
},
{
"name": "shadowclk.settings.js",
"url": "settings.js"
},
{
"name": "shadowclk.img",
"url": "app-icon.js",
"evaluate": true
}
],
"data": [{
"name": "shadowclk.json"
}]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

104
apps/shadowclk/settings.js Normal file
View File

@ -0,0 +1,104 @@
(function(back) {
let teletextColors = ["#000", "#f00", "#0f0", "#ff0", "#00f", "#f0f", "#0ff", "#fff"];
let teletextColorNames = ["Black", "Red", "Green", "Yellow", "Blue", "Magenta", "Cyan", "White"];
// Load and set default settings
let appSettings = Object.assign({
color: teletextColors[6],
theme: 'light',
}, require('Storage').readJSON("shadowclk.json", true) || {});
// Save settings to storage
function writeSettings() {
require('Storage').writeJSON("shadowclk.json", appSettings);
}
// Colors from 'Light BW' and 'Dark BW' themes
function createThemeColors(mode) {
let cl = x => g.setColor(x).getColor();
return mode ? {
fg: cl("#fff"),
bg: cl("#000"),
fg2: cl("#fff"),
bg2: cl("#004"),
fgH: cl("#fff"),
bgH: cl("#00f"),
dark: true
} : {
fg: cl("#000"),
bg: cl("#fff"),
fg2: cl("#000"),
bg2: cl("#cff"),
fgH: cl("#000"),
bgH: cl("#0ff"),
dark: false
};
}
// Switch theme and save to storage
function switchTheme(mode) {
if (mode === g.theme.dark) return;
let s = require('Storage').readJSON("setting.json", 1) || {};
s.theme = createThemeColors(mode);
require('Storage').writeJSON("setting.json", s);
updateTheme(mode);
}
// Update the current menu with the new theme
function updateTheme(mode) {
let newTheme = createThemeColors(mode);
g.theme = newTheme;
appSettings.theme = mode ? 'dark' : 'light';
writeSettings();
delete g.reset;
g._reset = g.reset;
g.reset = function(n) {
return g._reset().setColor(newTheme.fg).setBgColor(newTheme.bg);
};
g.clear = function(n) {
if (n) g.reset();
return g.clearRect(0, 0, g.getWidth(), g.getHeight());
};
g.clear(1);
Bangle.drawWidgets();
showMenu();
}
// Read the current system theme
function getCurrentTheme() {
let s = require('Storage').readJSON("setting.json", 1) || {};
if (!s.theme) {
return appSettings.theme; // fallback to appSettings.theme (light or dark)
}
return s.theme.dark ? 'dark' : 'light';
}
function showMenu() {
appSettings.theme = getCurrentTheme();
E.showMenu({
"": {
"title": "Shadow Clock"
},
"< Back": () => back(),
'Theme:': {
value: (appSettings.theme === 'dark'),
format: v => v ? "Dark" : "Light",
onchange: v => {
switchTheme(v);
}
},
'Color:': {
value: teletextColors.indexOf(appSettings.color),
min: 0,
max: 7,
onchange: v => {
appSettings.color = teletextColors[v];
writeSettings();
},
format: v => teletextColorNames[v]
}
});
}
// Initially show the menu
showMenu();
});

View File

@ -1,3 +1,4 @@
0.01: New app!
0.02: Bug fixes
0.03: Submitted to the app loader
0.03: Submitted to the app loader
0.04: Reduce battery consumption when the user's not looking

View File

@ -8,7 +8,7 @@ const BUTTON_ICONS = {
reset: heatshrink.decompress(atob("jEYwMA/4BB/+BAQPDAQPnAQIAKv///0///8j///EP//wAQQICBwQUCEhgyCHAQ+CIgI="))
};
let state = storage.readJSON(STATE_PATH);
let state = storage.readJSON(STATE_PATH, 1);
const STATE_DEFAULT = {
wasRunning: false, //If the stopwatch was ever running since being reset
sessionStart: 0, //When the stopwatch was first started
@ -157,7 +157,7 @@ function firstTimeStart(now, time) {
elapsedTime: 0,
};
lapFile = 'stlap-' + state.sessionStart + '.json';
setupTimerInterval();
setupTimerIntervalFast();
Bangle.buzz(200);
drawButtons();
}
@ -201,13 +201,15 @@ function start(now, time) {
state.elapsedTime += (state.pausedTime - state.startTime);
state.startTime = now;
state.running = true;
setupTimerInterval();
setupTimerIntervalFast();
Bangle.buzz(200);
drawTime();
drawButtons();
}
Bangle.on("touch", (button, xy) => {
setupTimerIntervalFast();
//In gesture mode, just turn on the light and then return
if (gestureMode) {
Bangle.setLCDPower(true);
@ -242,6 +244,8 @@ Bangle.on("touch", (button, xy) => {
});
Bangle.on('swipe', direction => {
setupTimerIntervalFast();
let now = (new Date()).getTime();
let time = getTime();
@ -272,12 +276,23 @@ setWatch(() => {
}, BTN1, { repeat: true });
let timerInterval;
let userWatching = false;
function setupTimerIntervalFast() {
userWatching = true;
setupTimerInterval();
setTimeout(() => {
userWatching = false;
setupTimerInterval();
}, 5000);
}
function setupTimerInterval() {
if (timerInterval !== undefined) {
clearInterval(timerInterval);
}
timerInterval = setInterval(drawTime, 10);
timerInterval = setInterval(drawTime, userWatching ? 10 : 1000);
}
function stopTimerInterval() {
@ -289,7 +304,7 @@ function stopTimerInterval() {
drawTime();
if (state.running) {
setupTimerInterval();
setupTimerIntervalFast();
}
//Save our state when the app is closed
@ -300,5 +315,8 @@ E.on('kill', () => {
}
});
// change interval depending of whether the user's looking
Bangle.on("twist", setupTimerIntervalFast);
Bangle.loadWidgets();
Bangle.drawWidgets();

View File

@ -1,7 +1,7 @@
{
"id": "stlap",
"name": "Stopwatch",
"version": "0.03",
"version": "0.04",
"description": "A stopwatch that remembers its state, with a lap timer and a gesture mode (enable by swiping)",
"icon": "icon.png",
"type": "app",
@ -20,5 +20,9 @@
"url": "icon.js",
"evaluate": true
}
],
"data": [
{"name":"stlap.state.json"},
{"wildcard":"stlap-*.json"}
]
}

View File

@ -1,3 +1,4 @@
0.01: 1st version: saves values to csv
0.02: added HTML interface
0.03: Added Stop/start recording, change BG color, filesize info
0.04: Support for negative degree, Min/Max, random for emulator, clean of code

View File

@ -1,5 +1,5 @@
# Temperature Monitor (with logging)
Temperature monitor that shows temperature on real time but also allows to store in a file for a later process.
Temperature / Thermometer monitor that not only shows degrees on real time but also allows to store this info in a file for a later process.
Compatible with BangleJS1,BangleJS2,and EMSCRIPTENx emulators

View File

@ -1,8 +1,8 @@
{
"id": "tempmonitor",
"name": "Temperature monitor",
"version": "0.03",
"description": "Displays the current temperature and stores in a CSV file",
"version": "0.04",
"description": "Another thermometer, besides displaying current temperature, stores it in a CSV file",
"icon": "app.png",
"tags": "tool",
"interface": "interface.html",

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,7 +1,7 @@
// Temperature monitor that saves a log of measures
// standalone ver for developer, to remove testing lines
// delimiter ; (excel) or , (oldscool)
/* REFACTOR and remove commented code related to
/* REFACTOR and remove commented code related to
SetUI, Layout, and setWatch( function(b) { }, BTN1, { repeat: true, edge:'falling' })
*/
{
@ -12,7 +12,7 @@ var history = [];
var readFreq=4000; //ms //PEND add to settings
if (v_mode_debug>0) var saveFreq=6000; //ms for testin 6sec
else var saveFreq=60000; //ms 1min
var v_saveToFile= new Boolean(true); //true save //false
var v_saveToFile= new Boolean(true); //true save //false
//with upload file º is not displayed properly
//with upload RAM º is displayed
var v_t_symbol="";//ºC
@ -23,6 +23,8 @@ var v_model=process.env.BOARD;
var v_color_erase=g.getBgColor(); //original BG color overwritten on SetVariables
var v_color=g.getColor();//original FG color
var id_rec_intv; //var for the recording interval
var v_t_max=-50; //preset with an opposite and impossible record measure
var v_t_min=70;
if (readFreq>saveFreq) console.log("Read refresh freq should be higher than saving");
if (v_mode_debug>0) console.log("original BG/FG color="+v_color_erase+" / "+v_color);
@ -32,281 +34,303 @@ if (v_mode_debug>0) console.log("original BG/FG color="+v_color_erase+" / "+v_co
function SetVariables(){
//EMSCRIPTEN,EMSCRIPTEN2
if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') {
v_font_size1=16;
v_font_size2=50;
}else{
//Banglejs2 or others
v_font_size1=11; //too small?
v_font_size2=40;
}
//overwriting default BG, is better detect?
if (g.theme.dark==1) v_color_erase=0x0000; //dynamic; //bg black
else if (g.theme.dark==0) v_color_erase=0xFFFF; //dynamic; //bg white
v_font_size1=16;
v_font_size2=50;
}else{
//Banglejs2 or others
v_font_size1=11; //too small?
v_font_size2=40;
}
//overwriting default BG, is better detect?
if (g.theme.dark==1) v_color_erase=0x0000; //dynamic; //bg black
else if (g.theme.dark==0) v_color_erase=0xFFFF; //dynamic; //bg white
}
//print result
function printTemperature(v_temp) {
if (v_mode_debug>1) console.log("v_temp in "+v_temp+" entries "+v_saved_entries);
ClearBox();
//g.setFont("6x8",2).setFontAlign(0,0);
g.setFontVector(v_font_size1).setFontAlign(0,0);
var x = (rect.x+(rect.x2-60))/2;//-60 space for graph and layout buttons
var y = (rect.y+rect.y2)/2 + 20;
if (v_mode_debug>1) console.log("v_temp in "+v_temp+" entries "+v_saved_entries);
if (v_saveToFile==true) {
// if (v_mode_debug>0) console.log("prev color="+v_color);
printInfo("Recording : "+v_saved_entries, '#CC3333',x,rect.y+30);
//g.setColor('#CC3333'); //red
// g.drawString("Recording : "+v_saved_entries, x, rect.y+35);
//g.setColor(v_color);//restore default color
}
else printInfo("Rec paused : "+v_saved_entries, v_color,x,rect.y+30);
//else g.drawString("Rec paused : "+v_saved_entries, x, rect.y+35);
//space for printing info
g.drawString("Temperature:", x, rect.y+45+(v_font_size1*2));
//dynamic font (g.getWidth() > 200 ? 60 : 40)
g.setFontVector(v_font_size2).setFontAlign(0,0);
// Avg of temperature readings
while (history.length>4) history.shift();
history.push(v_temp);
var avrTemp = E.sum(history) / history.length;
//var t = require('locale').temp(avrTemp);
//.replace("'","°");
lastMeasure=avrTemp.toString();
if (lastMeasure.length>4) lastMeasure=lastMeasure.substr(0,4);
//DRAW temperature in the center
//remove g.drawString(" ", x-20, y);
g.drawString(v_temp+v_t_symbol, x, y);
g.flip();
// Avg of temperature readings
while (history.length>4) history.shift();
history.push(v_temp);
var avrTemp = E.sum(history) / history.length;
//var t = require('locale').temp(avrTemp);
//.replace("'","°");
lastMeasure=avrTemp.toString();
if (lastMeasure.length>4) lastMeasure=lastMeasure.substr(0,4);
ClearBox();
//g.setFont("6x8",2).setFontAlign(0,0);
g.setFontVector(v_font_size1).setFontAlign(0,0);
var x = (rect.x+(rect.x2-60))/2;//-60 space for graph and layout buttons
var y = (rect.y+rect.y2)/2 + 20;
if (v_saveToFile==true) {
// if (v_mode_debug>0) console.log("prev color="+v_color);
printInfo("Recording : "+v_saved_entries, '#CC3333',x,rect.y+30);
//g.setColor('#CC3333'); //red
// g.drawString("Recording : "+v_saved_entries, x, rect.y+35);
//g.setColor(v_color);//restore default color
}
else printInfo("Rec paused : "+v_saved_entries, v_color,x,rect.y+30);
//else g.drawString("Rec paused : "+v_saved_entries, x, rect.y+35);
//space for printing info
g.drawString("Temperature:", x, rect.y+45+(v_font_size1*2));
if (v_temp>v_t_max) {
g.setColor(v_color_erase);
g.drawString(v_t_max.toString().substr(0,5), rect.x2-40,(rect.y2/2)-30);
v_t_max=v_temp;
g.setColor(v_color);
}
g.drawString(v_t_max.toString().substr(0,5), rect.x2-40,(rect.y2/2)-30);
if (v_temp<v_t_min) {
g.setColor(v_color_erase);
g.drawString(v_t_min.toString().substr(0,5), rect.x2-40,(rect.y2/2)+50);
v_t_min=v_temp;
g.setColor(v_color);
}
g.drawString(v_t_min.toString().substr(0,5), rect.x2-40,(rect.y2/2)+50);
//dynamic font (g.getWidth() > 200 ? 60 : 40)
g.setFontVector(v_font_size2).setFontAlign(0,0);
//DRAW temperature in the center
//5 char required for negative degrees
g.drawString((v_temp.toString().substr(0,5))+v_t_symbol, x, y);
g.flip();
}
// from: BJS2 pressure sensor, BJS1 inbuilt thermistor
function getTemperature() {
if(v_model.substr(0,10)!='EMSCRIPTEN'){
if (Bangle.getPressure) {
Bangle.getPressure().then(p =>{if (p) printTemperature(p);});
} else printTemperature(E.getTemperature());
}
else printTemperature(11.25);//fake temperature medition for emulators
if(v_model.substr(0,10)!='EMSCRIPTEN'){
if (Bangle.getPressure) {
Bangle.getPressure().then(p =>{if (p) printTemperature(p);});
} else printTemperature(E.getTemperature());
}
else printTemperature(-11.2+Math.random());//fake temperature medition for emulators
}
/* Note that it changes BG and also FG to an opposite*/
function changeBGcolor(){
//pend to refactor
if (v_mode_debug>1) console.log("before BG/FG "+v_color_erase+" /"+v_color);
v_color_erase=0xFFFF-v_color_erase;
v_color=0xFFFF-v_color;
if (v_mode_debug>1) console.log("after result BG/FG "+v_color_erase+" /"+v_color);
//g.setColor(color_result);
g.setBgColor(v_color_erase);// 0 white, 1 black
g.setColor(v_color);
//move to event?
ClearScreen();
ClearBox();
drawGraph();
getTemperature();
//setDrawLayout(); //uncomment if layout can work with setUI
//g.clear();//impact on widgets
function changeBGcolor(){
//pend to refactor
if (v_mode_debug>1) console.log("before BG/FG "+v_color_erase+" /"+v_color);
v_color_erase=0xFFFF-v_color_erase;
v_color=0xFFFF-v_color;
if (v_mode_debug>1) console.log("after result BG/FG "+v_color_erase+" /"+v_color);
//g.setColor(color_result);
g.setBgColor(v_color_erase);// 0 white, 1 black
g.setColor(v_color);
//move to event?
ClearScreen();
ClearBox();//?
getTemperature();
drawGraph();
//setDrawLayout(); //uncomment if layout can work with setUI
//g.clear();//impact on widgets
}
function saveToFile(){
//input global vars: lastMeasure
var a=new Date();
var strlastSaveTime=new String();
strlastSaveTime=a.toISOString();
//strlastSaveTime=strlastSaveTime.concat(a.getFullYear(),a.getMonth()+1,a.getDate(),a.getHours(),a.getMinutes());;
if (v_mode_debug>1) console.log("saving="+strlastSaveTime+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure);
if (v_saveToFile==true){
//write(strlastSaveTime+";"+
//var f = require("Storage").open(v_filename,"r");
// f=require("Storage").read(v_filename+"\1");//suffix required load completely!!
//note that .read uses Storage Class .open uses StorageFile Class , difference in file chunks
// if (v_mode_debug>0) console.log("f "+f);
var f = require("Storage").open(v_filename,"r");
if ((v_mode_debug>0) && (v_saved_entries==0)) console.log("file info:"+f);
if (f.len>0) {
if (!f) {
require("Storage").open(v_filename,"w").write("Month;Day;Time;Temp"+"\n");
if (v_mode_debug>0) console.log("not exist but created "+f);
}
else{
require("Storage").open(v_filename,"a").write((a.getMonth()+1)+";"+a.getDate()+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure+"\n");
//(getTime()+",");
v_saved_entries=v_saved_entries+1;
if (v_mode_debug>1) console.log("append to already exist "+f.name+" , "+v_saved_entries);
}
}
}
else if (v_mode_debug>0) console.log("recording mode stopped");
//input global vars: lastMeasure
var a=new Date();
var strlastSaveTime=new String();
strlastSaveTime=a.toISOString();
//strlastSaveTime=strlastSaveTime.concat(a.getFullYear(),a.getMonth()+1,a.getDate(),a.getHours(),a.getMinutes());;
if (v_mode_debug>1) console.log("saving="+strlastSaveTime+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure);
if (v_saveToFile==true){
//write(strlastSaveTime+";"+
//var f = require("Storage").open(v_filename,"r");
// f=require("Storage").read(v_filename+"\1");//suffix required load completely!!
//note that .read uses Storage Class .open uses StorageFile Class , difference in file chunks
// if (v_mode_debug>0) console.log("f "+f);
var f = require("Storage").open(v_filename,"r");
if ((v_mode_debug>0) && (v_saved_entries==0)) console.log("file info:"+f);
if (f.len>0) {
if (!f) {
require("Storage").open(v_filename,"w").write("Month;Day;Time;Temp"+"\n");
if (v_mode_debug>0) console.log("not exist but created "+f);
}
else{
require("Storage").open(v_filename,"a").write((a.getMonth()+1)+";"+a.getDate()+";"+a.getHours()+":"+a.getMinutes()+";"+lastMeasure+"\n");
//(getTime()+",");
v_saved_entries=v_saved_entries+1;
if (v_mode_debug>1) console.log("append to already exist "+f.name+" , "+v_saved_entries);
}
}
}
else if (v_mode_debug>0) console.log("recording mode stopped");
}
function drawGraph(){
var img_obj_thermo = {
width : 36, height : 36, bpp : 3,
transparent : 0,
buffer : require("heatshrink").decompress(atob("AEFt2AMKm3bsAMJjdt23ABhEB+/7tgaJ///DRUP//7tuADRP923YDRXbDRfymwaJhu/koaK7eyiwaK3cLDRlWDRY1NKBY1Ztu5kjmJg3cyVI7YMHgdu5Mkyu2fxHkyVJjdgDRFJkmRDRPsDQNbDQ5QBGoONKBJrBoxQIQwO2eRcbtu24AMIFIQLJAH4AMA=="))
};
g.drawImage(img_obj_thermo,rect.x2-60,rect.y2/2);
g.flip();
if (v_mode_debug>1) console.log("drawGraph");
var img_obj_thermo = {
width : 36, height : 36, bpp : 3,
transparent : 0,
buffer : require("heatshrink").decompress(atob("AEFt2AMKm3bsAMJjdt23ABhEB+/7tgaJ///DRUP//7tuADRP923YDRXbDRfymwaJhu/koaK7eyiwaK3cLDRlWDRY1NKBY1Ztu5kjmJg3cyVI7YMHgdu5Mkyu2fxHkyVJjdgDRFJkmRDRPsDQNbDQ5QBGoONKBJrBoxQIQwO2eRcbtu24AMIFIQLJAH4AMA=="))
};
g.drawImage(img_obj_thermo,rect.x2-60,rect.y2/2);
g.flip();
}
function ClearScreen(){
//avoid widget areas
g.setBgColor(v_color_erase);
g.clearRect(rect.x, rect.y+24, rect.x2, rect.y2-24);
g.flip();
//avoid widget areas
g.setBgColor(v_color_erase);
g.clearRect(rect.x, rect.y+24, rect.x2, rect.y2-24);
g.flip();
}
function ClearBox(){
//custom boxarea , left space for static graph at right
g.setBgColor(v_color_erase);
g.clearRect(rect.x, rect.y+24, rect.x2-60, rect.y2-24);
g.flip();
//custom boxarea , left space for static graph at right
g.setBgColor(v_color_erase);
g.clearRect(rect.x, rect.y+24, rect.x2-60, rect.y2-24);
g.flip();
}
function introPage(){
//g.setFont("6x8",2).setFontAlign(0,0);
g.setFontVector(v_font_size1).setFontAlign(-1,0);
//x alignment. -1=left (default), 0=center, 1=right
var x=3;
//dynamic positions as height for BJS1 is double than BJS2
var y = (rect.y+rect.y2)/2 + 10;
g.drawString(" Default values ", x, y - ((v_font_size1*3)+2));
g.drawString("--------------------", x, y - ((v_font_size1*2)+2));
g.drawString("Mode debug: "+v_mode_debug, x, y - ((v_font_size1*1)+2));
g.drawString("Read freq(ms): "+readFreq, x, y );
g.drawString("Save to file: "+v_saveToFile, x, y+ ((v_font_size1*1)+2) );
g.drawString("Save freq(ms):"+saveFreq, x, y+((v_font_size1*2)+2) );
fr=require("Storage").read(v_filename+"\1");//suffix required
if (fr) g.drawString("Filesize:"+fr.length.toString()+"kb", x, y+((v_font_size1*3)+2) );
else g.drawString("File not exist", x, y+((v_font_size1*3)+2));
//g.setFont("6x8",2).setFontAlign(0,0);
g.setFontVector(v_font_size1).setFontAlign(-1,0);
//x alignment. -1=left (default), 0=center, 1=right
var x=3;
//dynamic positions as height for BJS1 is double than BJS2
var y = (rect.y+rect.y2)/2 + 10;
g.drawString(" Default values ", x, y - ((v_font_size1*3)+2));
g.drawString("--------------------", x, y - ((v_font_size1*2)+2));
g.drawString("Mode debug: "+v_mode_debug, x, y - ((v_font_size1*1)+2));
g.drawString("Read frq(ms): "+readFreq, x, y );
g.drawString("Save file: "+v_saveToFile, x, y+ ((v_font_size1*1)+2) );
g.drawString("Save frq(ms):"+saveFreq, x, y+((v_font_size1*2)+2) );
fr=require("Storage").read(v_filename+"\1");//suffix required
if (fr) g.drawString("Filesize:"+fr.length.toString()+"kb", x, y+((v_font_size1*3)+2) );
else g.drawString("File not exist", x, y+((v_font_size1*3)+2));
}
function printInfo(pmsg, pcolor,px,py){
g.setColor(pcolor);
g.setFontVector(v_font_size1).setFontAlign(0,0);
g.drawString(pmsg, px,py+v_font_size1);
g.setColor(v_color);//restore default color
g.setColor(pcolor);
g.setFontVector(v_font_size1).setFontAlign(0,0);
g.drawString(pmsg, px,py+v_font_size1);
g.setColor(v_color);//restore default color
}
function toggleRecMode(duration, exectime){
//bydefault float, standard epoch requires *1000
if (v_mode_debug>0) console.log("duration"+duration);
if (duration>2) { //delete file
var x = (rect.x+(rect.x2-60))/2;
printInfo("Deleting file",'#CC3333',x, rect.y+32+v_font_size1);
// g.setColor('#CC3333'); //red
//bydefault float, standard epoch requires *1000
if (v_mode_debug>0) console.log("duration"+duration);
if (duration>2) { //delete file
var x = (rect.x+(rect.x2-60))/2;
printInfo("Deleting file",'#CC3333',x, rect.y+32+v_font_size1);
// g.setColor('#CC3333'); //red
//too long "Deleting file: "+v_filename,
// for StorageFiles created with require("Storage").open(filename, ...)
//require("Storage").erase(v_filename);
//TODO refactor in a new function
//var mifile = require("Storage").open(v_filename,"w");
var mifile = require("Storage").open("temphistory.csv","w");
var v_output=mifile.erase();
//mifile.StorageFile.erase();
if (v_mode_debug>0) console.log("output"+v_output);
setTimeout(function() { if (v_mode_debug>0) console.log("pause for 1 sec");},1000);
return; //leave this function
}
if (v_saveToFile) v_saveToFile=false;
else v_saveToFile=true;
if (v_mode_debug>0) console.log("recording? "+v_saveToFile);
setRecordingFreq();
//too long "Deleting file: "+v_filename,
// for StorageFiles created with require("Storage").open(filename, ...)
//require("Storage").erase(v_filename);
//TODO refactor in a new function
//var mifile = require("Storage").open(v_filename,"w");
var mifile = require("Storage").open("temphistory.csv","w");
var v_output=mifile.erase();
//mifile.StorageFile.erase();
if (v_mode_debug>0) console.log("output"+v_output);
setTimeout(function() { if (v_mode_debug>0) console.log("pause for 1 sec");},1000);
return; //leave this function
}
if (v_saveToFile) v_saveToFile=false;
else v_saveToFile=true;
if (v_mode_debug>0) console.log("recording? "+v_saveToFile);
setRecordingFreq();
}
function setRecordingFreq(){
if (v_saveToFile==true) { //TODO now start on false btn will no enable
id_rec_intv=setInterval(function() {
saveToFile();
}, saveFreq); //ms
if (v_mode_debug>0) console.log("interval id / frq"+id_rec_intv+" / "+saveFreq);
}
else if (id_rec_intv){
clearInterval(id_rec_intv);
if (v_mode_debug>0) console.log("rec interval removed, id "+id_rec_intv);
id_rec_intv=0; // to reset var
}
if (v_saveToFile==true) { //TODO now start on false btn will no enable
id_rec_intv=setInterval(function() {
saveToFile();
}, saveFreq); //ms
if (v_mode_debug>0) console.log("interval id / frq"+id_rec_intv+" / "+saveFreq);
}
else if (id_rec_intv){
clearInterval(id_rec_intv);
if (v_mode_debug>0) console.log("rec interval removed, id "+id_rec_intv);
id_rec_intv=0; // to reset var
}
}
function UserInput(){
//theoretically incompatible with Layout
Bangle.setUI({
mode : "custom",
//adds a back icon on top widget area
back : function() {load();},
//touch : function(n,e) {}, // optional - handler for 'touch' events
// righ/Left 1/-1 , updown
swipe : function(dir_rl,dir_ud) {
if(dir_rl == 1) {
if (v_mode_debug>0) console.log("swipe right: ");
getFileInfo(v_filename);
}
else if (dir_rl == -1){
if (v_mode_debug>0) console.log("swipe left: ");
changeBGcolor();
}
},
touch : function(tzone,tobj){
if ((process.env.HWVERSION == 2)&&(v_mode_debug>0)){
console.log("tobj x,y,type : "+tobj.x+" "+tobj.y+" "+tobj.type);
}
switch(tzone){
//case 1: //left , back managed by setUI
case 2: // right disable/enable recording
toggleRecMode(0); //toggleRecMode(duration, exectime)
break;
// case 3: console.log("Touch 3 aka 1+2 not for BJS1 emul");//center 1+2
// break;
}
},
//inferior to
btn : function(btn) {
if(btn == 1) {
if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') toggleRecMode(1); //console.log("btn1 BJS1");
else mainBtnShortcut(); //console.log("btn1 BJS2");
}
else if (btn == 2) mainBtnShortcut(); //console.log("btn2 BJS1");
else if (btn == 3) changeBGcolor(); //console.log("btn3 BJS1");
}
}); //endof setUI
function UserInput(){
//theoretically incompatible with Layout
Bangle.setUI({
mode : "custom",
//adds a back icon on top widget area
back : function() {load();},
//touch : function(n,e) {}, // optional - handler for 'touch' events
// righ/Left 1/-1 , updown
swipe : function(dir_rl,dir_ud) {
if(dir_rl == 1) {
if (v_mode_debug>0) console.log("swipe right: ");
getFileInfo(v_filename);
}
else if (dir_rl == -1){
if (v_mode_debug>0) console.log("swipe left: ");
changeBGcolor();
}
},
touch : function(tzone,tobj){
if ((process.env.HWVERSION == 2)&&(v_mode_debug>0)){
console.log("tobj x,y,type : "+tobj.x+" "+tobj.y+" "+tobj.type);
}
switch(tzone){
//case 1: //left , back managed by setUI
case 2: // right disable/enable recording
toggleRecMode(0); //toggleRecMode(duration, exectime)
break;
// case 3: console.log("Touch 3 aka 1+2 not for BJS1 emul");//center 1+2
// break;
}
},
//inferior to
btn : function(btn) {
if(btn == 1) {
if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') toggleRecMode(1); //console.log("btn1 BJS1");
else mainBtnShortcut(); //console.log("btn1 BJS2");
}
else if (btn == 2) mainBtnShortcut(); //console.log("btn2 BJS1");
else if (btn == 3) changeBGcolor(); //console.log("btn3 BJS1");
}
}); //endof setUI
}
function mainBtnShortcut() {
//if messages app installed shortcut otherwise default access to launcher
if (require("Storage").read("messagegui.app.js")===undefined)
{
if (require("Storage").read("messagelist.app.js")===undefined) Bangle.showLauncher(); // implies btn2(js1) btn(js2)- launcher
else if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') load("messagelist.app.js");
else load("messagelist.app.js");
}
else if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') load("messagegui.app.js");
else load("messagegui.app.js");
}
function mainBtnShortcut() {
//if messages app installed shortcut otherwise default access to launcher
if (require("Storage").read("messagegui.app.js")===undefined)
{
if (require("Storage").read("messagelist.app.js")===undefined) Bangle.showLauncher(); // implies btn2(js1) btn(js2)- launcher
else if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') load("messagelist.app.js");
else load("messagelist.app.js");
}
else if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') load("messagegui.app.js");
else load("messagegui.app.js");
}
// Show file size
function getFileInfo(v_filename) {
var f = require("Storage").open(v_filename,"r");
//todo refactor and reuse common code
g.setFontVector(v_font_size1).setFontAlign(0,0);
var x = (rect.x+(rect.x2-60))/2;
printInfo("file size:"+f.len,v_color,x, rect.y+32+v_font_size1);
// g.drawString("file size:"+f.len, x, rect.y+37+v_font_size1);
if (v_mode_debug>0) console.log("file "+v_filename+" size: "+f.len);
function getFileInfo(v_filename) {
var f = require("Storage").open(v_filename,"r");
//todo refactor and reuse common code
g.setFontVector(v_font_size1).setFontAlign(0,0);
var x = (rect.x+(rect.x2-60))/2;
printInfo("file size:"+f.len,v_color,x, rect.y+32+v_font_size1);
// g.drawString("file size:"+f.len, x, rect.y+37+v_font_size1);
if (v_mode_debug>0) console.log("file "+v_filename+" size: "+f.len);
}// not used
//MAIN
SetVariables();
Bangle.loadWidgets();
ClearScreen();
introPage();
//setDrawLayout(); //uncomment if layout can work with setUI
UserInput(); //inc SetUI and back icon
setInterval(function() {
getTemperature();
getTemperature();
}, readFreq); //ms
//??need
drawGraph();
setRecordingFreq();
}

View File

@ -1 +1 @@
{"id":"tempmonitor","name":"tempmonitor","src":"tempmonitor.app.js","icon":"tempmonitor.img","version":"0.03","files":"tempmonitor.info,tempmonitor.app.js,tempmonitor.img"}
{"id":"tempmonitor","name":"tempmonitor","src":"tempmonitor.app.js","icon":"tempmonitor.img","version":"0.04","files":"tempmonitor.info,tempmonitor.app.js,tempmonitor.img"}

View File

@ -0,0 +1 @@
0.01: New Widget!

View File

@ -0,0 +1,13 @@
{ "id": "widclkinfo",
"name": "Clock Info Widget",
"version":"0.01",
"description": "Use 'Clock Info' in the Widget bar. Tap on the widget to select, then drag up/down/left/right to choose what information is displayed.",
"icon": "widget.png",
"screenshots" : [ { "url":"screenshot.png" }],
"type": "widget",
"tags": "widget,clkinfo",
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"widclkinfo.wid.js","url":"widget.js"}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

49
apps/widclkinfo/widget.js Normal file
View File

@ -0,0 +1,49 @@
if (!require("clock_info").loadCount) { // don't load if a clock_info was already loaded
// Load the clock infos
let clockInfoItems = require("clock_info").load();
// Add the
let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, {
// Add the dimensions we're rendering to here - these are used to detect taps on the clock info area
x : 0, y: 0, w: 72, h:24,
// You can add other information here you want to be passed into 'options' in 'draw'
// This function draws the info
draw : (itm, info, options) => {
// itm: the item containing name/hasRange/etc
// info: data returned from itm.get() containing text/img/etc
// options: options passed into addInteractive
clockInfoInfo = info;
if (WIDGETS["clkinfo"])
WIDGETS["clkinfo"].draw(WIDGETS["clkinfo"]);
}
});
let clockInfoInfo; // when clockInfoMenu.draw is called we set this up
// The actual widget we're displaying
WIDGETS["clkinfo"] = {
area:"tl",
width: clockInfoMenu.w,
draw:function(e) {
clockInfoMenu.x = e.x;
clockInfoMenu.y = e.y;
var o = clockInfoMenu;
// Clear the background
g.reset();
// indicate focus - make background reddish
//if (clockInfoMenu.focus) g.setBgColor(g.blendColor(g.theme.bg, "#f00", 0.25));
if (clockInfoMenu.focus) g.setColor("#f00");
g.clearRect(o.x, o.y, o.x+o.w-1, o.y+o.h);
if (clockInfoInfo) {
var x = o.x;
if (clockInfoInfo.img) {
g.drawImage(clockInfoInfo.img, x,o.y); // draw the image
x+=24;
}
var availableWidth = o.x+clockInfoMenu.w - (x+2);
g.setFont("6x8:2").setFontAlign(-1,0);
if (g.stringWidth(clockInfoInfo.text) > availableWidth)
g.setFont("6x8");
g.drawString(clockInfoInfo.text, x+2,o.y+12); // draw the text
}
}
};
}

BIN
apps/widclkinfo/widget.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

21
apps/widhid/README.md Normal file
View File

@ -0,0 +1,21 @@
# Description
A music control widget based on [Swipe Bluetooth Music Controls] (based on [Bluetooth Music Controls]).
By operating as a widget, you can control music without leaving your current app (e.g. on a run, bike ride or just watching the clock).
[Swipe Bluetooth Music Controls]: https://github.com/espruino/BangleApps/tree/master/apps/hidmsicswipe
[Bluetooth Music Controls]: https://github.com/espruino/BangleApps/tree/master/apps/hidmsic
# Usage
Swipe down to enable - note the icon changes from blue to orange, indicating it's listening for your instruction. Then drag up/down for volume, left/right for previous and next and tap for play/pause.
All other watch interaction is disabled for 3 seconds, to prevent clashing taps/drags - this period is extended as you continue to alter the volume, play/pause and jump between tracks.
# Setup / Technical details
Note that HID must be enabled in settings. Then provided you're paired with your phone/computer, the widget icon will appear and you can control music from your clock face!
The app disables all other drag and tap handlers while this widget is "active" (in a similar manner to [`backswipe`](https://github.com/espruino/BangleApps/pull/2524#issuecomment-1406230564) and [`lightswitch`](https://github.com/espruino/Espruino/issues/2151#issuecomment-1042423211)).

1
apps/widhid/icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkBiIA/AEhtDNSSHGCyIbFGJ0QFAowIA48QE4oGBBAomBHAxXHA4IJED5IXJCAcQIAxGGC4YKEI44HCBAxAGO4wXBB4JYGNRBfHC/6HFB4wXHUA6YIC4oOCGA6YGU4quHJ5LXGdJIXNF65fIC5AQFQorHJXxwXJK5xGJC65GsgJG/Iw4uUfgIuUC4QWTIwIusLq4WBFy50tC1YXBCyoA/ADw="))

BIN
apps/widhid/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

16
apps/widhid/metadata.json Normal file
View File

@ -0,0 +1,16 @@
{
"id": "widhid",
"name": "Bluetooth Music Swipe Control Widget",
"shortName": "BLE Swipe Widget",
"version": "0.01",
"description": "Based on Swipe Bluetooth Music Controls (based on Bluetooth Music Controls). Swipe down to enable, then swipe up/down for volume, left/right for previous and next and tap for play/pause. Enable HID in settings, pair with your phone/computer, then use this widget to control music from your watch!",
"icon": "icon.png",
"readme": "README.md",
"type": "widget",
"tags": "widget,bluetooth,music",
"supports": ["BANGLEJS2"],
"storage": [
{"name":"widhid.wid.js","url":"wid.js"},
{"name":"widhid.img","url":"icon.js","evaluate":true}
]
}

176
apps/widhid/wid.js Normal file
View File

@ -0,0 +1,176 @@
(function () {
var settings = require("Storage").readJSON("setting.json", true) || { HID: false };
if (settings.HID !== "kbmedia") {
console.log("widhid: can't enable, HID setting isn't \"kbmedia\"");
return;
}
delete settings;
var anchor = { x: 0, y: 0 };
var start = { x: 0, y: 0 };
var dragging = false;
var activeTimeout;
var waitForRelease = true;
var onSwipe = (function (_lr, ud) {
if (Bangle.CLKINFO_FOCUS)
return;
if (!activeTimeout && ud > 0) {
listen();
Bangle.buzz(20);
}
});
var onDrag = (function (e) {
if (Bangle.CLKINFO_FOCUS)
return;
if (e.b === 0) {
var wasDragging = dragging;
dragging = false;
if (waitForRelease) {
waitForRelease = false;
return;
}
if (!wasDragging
|| (Math.abs(e.x - anchor.x) < 2 && Math.abs(e.y - anchor.y) < 2)) {
toggle();
onEvent();
return;
}
}
if (waitForRelease)
return;
if (e.b && !dragging) {
dragging = true;
setStart(e);
Object.assign(anchor, start);
return;
}
var dx = e.x - start.x;
var dy = e.y - start.y;
if (Math.abs(dy) > 25 && Math.abs(dx) > 25) {
setStart(e);
return;
}
if (dx > 40) {
next();
onEvent();
waitForRelease = true;
}
else if (dx < -40) {
prev();
onEvent();
waitForRelease = true;
}
else if (dy > 30) {
down();
onEvent();
setStart(e);
}
else if (dy < -30) {
up();
onEvent();
setStart(e);
}
});
var setStart = function (_a) {
var x = _a.x, y = _a.y;
start.x = x;
start.y = y;
};
var onEvent = function () {
Bangle.buzz(20);
listen();
};
var listen = function () {
var wasActive = !!activeTimeout;
if (!wasActive) {
suspendOthers();
waitForRelease = true;
Bangle.on("drag", onDrag);
redraw();
}
if (activeTimeout)
clearTimeout(activeTimeout);
activeTimeout = setTimeout(function () {
activeTimeout = undefined;
Bangle.removeListener("drag", onDrag);
resumeOthers();
redraw();
}, 3000);
};
var redraw = function () { return setTimeout(Bangle.drawWidgets, 50); };
var connected = NRF.getSecurityStatus().connected;
WIDGETS["hid"] = {
area: "tr",
sortorder: -20,
draw: function () {
if (this.width === 0)
return;
g.drawImage(activeTimeout
? require("heatshrink").decompress(atob("jEYxH+AEfH44XXAAYXXDKIXZDYp3pC/6KHUMwWHC/4XvUy4YGdqoA/AFoA=="))
: require("heatshrink").decompress(atob("jEYxH+AEcdjoXXAAYXXDKIXZDYp3pC/6KHUMwWHC/4XvUy4YGdqoA/AFoA==")), this.x + 2, this.y + 2);
},
width: connected ? 24 : 0,
};
if (connected)
Bangle.on("swipe", onSwipe);
delete connected;
NRF.on("connect", function () {
WIDGETS["hid"].width = 24;
Bangle.on("swipe", onSwipe);
redraw();
});
NRF.on("disconnect", function () {
WIDGETS["hid"].width = 0;
Bangle.removeListener("swipe", onSwipe);
redraw();
});
var sendHid = function (code) {
NRF.sendHIDReport([1, code], function () { return NRF.sendHIDReport([1, 0]); });
};
var next = function () { return sendHid(0x01); };
var prev = function () { return sendHid(0x02); };
var toggle = function () { return sendHid(0x10); };
var up = function () { return sendHid(0x40); };
var down = function () { return sendHid(0x80); };
var touchEvents = {
tap: null,
gesture: null,
aiGesture: null,
swipe: null,
touch: null,
drag: null,
stroke: null,
};
var suspendOthers = function () {
for (var event in touchEvents) {
var handlers = Bangle["#on".concat(event)];
if (!handlers)
continue;
var newEvents = void 0;
if (handlers instanceof Array)
newEvents = handlers.slice();
else
newEvents = [handlers];
for (var _i = 0, newEvents_1 = newEvents; _i < newEvents_1.length; _i++) {
var handler = newEvents_1[_i];
Bangle.removeListener(event, handler);
}
touchEvents[event] = newEvents;
}
};
var resumeOthers = function () {
for (var event in touchEvents) {
var handlers = touchEvents[event];
touchEvents[event] = null;
if (handlers)
for (var _i = 0, handlers_1 = handlers; _i < handlers_1.length; _i++) {
var handler = handlers_1[_i];
try {
Bangle.on(event, handler);
}
catch (e) {
console.log("couldn't restore \"".concat(event, "\" handler:"), e);
}
}
}
};
})();

199
apps/widhid/wid.ts Normal file
View File

@ -0,0 +1,199 @@
(() => {
const settings: Settings = require("Storage").readJSON("setting.json", true) || { HID: false } as Settings;
if (settings.HID !== "kbmedia") {
console.log("widhid: can't enable, HID setting isn't \"kbmedia\"");
return;
}
// @ts-ignore
delete settings;
let anchor = {x:0,y:0};
let start = {x:0,y:0};
let dragging = false;
let activeTimeout: number | undefined;
let waitForRelease = true;
const onSwipe = ((_lr, ud) => {
if((Bangle as BangleExt).CLKINFO_FOCUS) return;
if(!activeTimeout && ud! > 0){
listen();
Bangle.buzz(20);
}
}) satisfies SwipeCallback;
const onDrag = (e => {
if((Bangle as BangleExt).CLKINFO_FOCUS) return;
if(e.b === 0){
// released
const wasDragging = dragging;
dragging = false;
if(waitForRelease){
waitForRelease = false;
return;
}
if(!wasDragging // i.e. tap
|| (Math.abs(e.x - anchor.x) < 2 && Math.abs(e.y - anchor.y) < 2))
{
toggle();
onEvent();
return;
}
}
if(waitForRelease) return;
if(e.b && !dragging){
dragging = true;
setStart(e);
Object.assign(anchor, start);
return;
}
const dx = e.x - start.x;
const dy = e.y - start.y;
if(Math.abs(dy) > 25 && Math.abs(dx) > 25){
// diagonal, ignore
setStart(e);
return;
}
// had a drag in a single axis
if(dx > 40){ next(); onEvent(); waitForRelease = true; }
else if(dx < -40){ prev(); onEvent(); waitForRelease = true; }
else if(dy > 30){ down(); onEvent(); setStart(e); }
else if(dy < -30){ up(); onEvent(); setStart(e); }
}) satisfies DragCallback;
const setStart = ({ x, y }: { x: number, y: number }) => {
start.x = x;
start.y = y;
};
const onEvent = () => {
Bangle.buzz(20); // feedback event sent
listen(); // had an event, keep listening for more
};
const listen = () => {
const wasActive = !!activeTimeout;
if(!wasActive){
suspendOthers();
waitForRelease = true; // wait for first touch up before accepting gestures
Bangle.on("drag", onDrag);
redraw();
}
if(activeTimeout) clearTimeout(activeTimeout);
activeTimeout = setTimeout(() => {
activeTimeout = undefined;
Bangle.removeListener("drag", onDrag);
resumeOthers();
redraw();
}, 3000);
};
const redraw = () => setTimeout(Bangle.drawWidgets, 50);
const connected = NRF.getSecurityStatus().connected;
WIDGETS["hid"] = {
area: "tr",
sortorder: -20,
draw: function() {
if(this.width === 0) return;
g.drawImage(
activeTimeout
? require("heatshrink").decompress(atob("jEYxH+AEfH44XXAAYXXDKIXZDYp3pC/6KHUMwWHC/4XvUy4YGdqoA/AFoA=="))
: require("heatshrink").decompress(atob("jEYxH+AEcdjoXXAAYXXDKIXZDYp3pC/6KHUMwWHC/4XvUy4YGdqoA/AFoA==")),
this.x! + 2,
this.y! + 2
);
},
width: connected ? 24 : 0,
};
if(connected)
Bangle.on("swipe", onSwipe);
// @ts-ignore
delete connected;
NRF.on("connect", () => {
WIDGETS["hid"]!.width = 24;
Bangle.on("swipe", onSwipe);
redraw();
});
NRF.on("disconnect", () => {
WIDGETS["hid"]!.width = 0;
Bangle.removeListener("swipe", onSwipe);
redraw();
});
//const DEBUG = true;
const sendHid = (code: number) => {
//if(DEBUG) return;
NRF.sendHIDReport(
[1, code],
() => NRF.sendHIDReport([1, 0]),
);
};
const next = () => /*DEBUG ? console.log("next") : */ sendHid(0x01);
const prev = () => /*DEBUG ? console.log("prev") : */ sendHid(0x02);
const toggle = () => /*DEBUG ? console.log("toggle") : */ sendHid(0x10);
const up = () => /*DEBUG ? console.log("up") : */ sendHid(0x40);
const down = () => /*DEBUG ? console.log("down") : */ sendHid(0x80);
// similarly to the lightswitch app, we tangle with the listener arrays to
// disable event handlers
type Handler = () => void;
const touchEvents: {
[key: string]: null | Handler[]
} = {
tap: null,
gesture: null,
aiGesture: null,
swipe: null,
touch: null,
drag: null,
stroke: null,
};
const suspendOthers = () => {
for(const event in touchEvents){
const handlers: Handler[] | Handler | undefined
= (Bangle as any)[`#on${event}`];
if(!handlers) continue;
let newEvents;
if(handlers instanceof Array)
newEvents = handlers.slice();
else
newEvents = [handlers /* single fn */];
for(const handler of newEvents)
Bangle.removeListener(event, handler);
touchEvents[event] = newEvents;
}
};
const resumeOthers = () => {
for(const event in touchEvents){
const handlers = touchEvents[event];
touchEvents[event] = null;
if(handlers)
for(const handler of handlers)
try{
Bangle.on(event as any, handler);
}catch(e){
console.log(`couldn't restore "${event}" handler:`, e);
}
}
};
})()

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

Some files were not shown because too many files have changed in this diff Show More