updated sleepquiet

pull/3486/head
jla-42 2024-07-02 16:25:14 +02:00
parent adb7e8a924
commit 59f3726deb
344 changed files with 6493 additions and 4269 deletions

View File

@ -6,6 +6,8 @@ Bangle.js App Loader (and Apps)
* Try the **release version** at [banglejs.com/apps](https://banglejs.com/apps)
* Try the **development version** at [espruino.github.io](https://espruino.github.io/BangleApps/)
The release version is manually refreshed with regular intervals while the development version is continuously updated as new code is committed to this repository.
**All software (including apps) in this repository is MIT Licensed - see [LICENSE](LICENSE)** By
submitting code to this repository you confirm that you are happy with it being MIT licensed,
and that it is not licensed in another way that would make this impossible.
@ -403,7 +405,7 @@ in an iframe.
<link rel="stylesheet" href="../../css/spectre.min.css">
</head>
<body>
<script src="../../lib/interface.js"></script>
<script src="../../core/lib/interface.js"></script>
<div id="t">Loading...</div>
<script>
function onInit() {

View File

@ -1,203 +0,0 @@
const lintExemptions = require("./lint_exemptions.js");
module.exports = {
"env": {
// TODO: "espruino": false
// TODO: "banglejs": false
// For a prototype of the above, see https://github.com/espruino/BangleApps/pull/3237
},
"extends": "eslint:recommended",
"globals": {
// Methods and Fields at https://banglejs.com/reference
"Array": "readonly",
"ArrayBuffer": "readonly",
"ArrayBufferView": "readonly",
"Bangle": "readonly",
"BluetoothDevice": "readonly",
"BluetoothRemoteGATTCharacteristic": "readonly",
"BluetoothRemoteGATTServer": "readonly",
"BluetoothRemoteGATTService": "readonly",
"Boolean": "readonly",
"console": "readonly",
"DataView": "readonly",
"Date": "readonly",
"E": "readonly",
"Error": "readonly",
"Flash": "readonly",
"Float32Array": "readonly",
"Float64Array": "readonly",
"Function": "readonly",
"Graphics": "readonly",
"I2C": "readonly",
"Int16Array": "readonly",
"Int32Array": "readonly",
"Int8Array": "readonly",
"InternalError": "readonly",
"JSON": "readonly",
"Math": "readonly",
"Modules": "readonly",
"NRF": "readonly",
"Number": "readonly",
"Object": "readonly",
"OneWire": "readonly",
"Pin": "readonly",
"process": "readonly",
"Promise": "readonly",
"ReferenceError": "readonly",
"RegExp": "readonly",
"Serial": "readonly",
"SPI": "readonly",
"StorageFile": "readonly",
"String": "readonly",
"SyntaxError": "readonly",
"TFMicroInterpreter": "readonly",
"TypeError": "readonly",
"Uint16Array": "readonly",
"Uint24Array": "readonly",
"Uint32Array": "readonly",
"Uint8Array": "readonly",
"Uint8ClampedArray": "readonly",
"Unistroke": "readonly",
"Waveform": "readonly",
// Methods and Fields at https://banglejs.com/reference
"__FILE__": "readonly",
"analogRead": "readonly",
"analogWrite": "readonly",
"arguments": "readonly",
"atob": "readonly",
"Bluetooth": "readonly",
"BTN": "readonly",
"BTN1": "readonly",
"BTN2": "readonly",
"BTN3": "readonly",
"BTN4": "readonly",
"BTN5": "readonly",
"btoa": "readonly",
"changeInterval": "readonly",
"clearInterval": "readonly",
"clearTimeout": "readonly",
"clearWatch": "readonly",
"decodeURIComponent": "readonly",
"digitalPulse": "readonly",
"digitalRead": "readonly",
"digitalWrite": "readonly",
"dump": "readonly",
"echo": "readonly",
"edit": "readonly",
"encodeURIComponent": "readonly",
"eval": "readonly",
"getPinMode": "readonly",
"getSerial": "readonly",
"getTime": "readonly",
"global": "readonly",
"HIGH": "readonly",
"I2C1": "readonly",
"Infinity": "readonly",
"isFinite": "readonly",
"isNaN": "readonly",
"LED": "readonly",
"LED1": "readonly",
"LED2": "readonly",
"load": "readonly",
"LoopbackA": "readonly",
"LoopbackB": "readonly",
"LOW": "readonly",
"NaN": "readonly",
"parseFloat": "readonly",
"parseInt": "readonly",
"peek16": "readonly",
"peek32": "readonly",
"peek8": "readonly",
"pinMode": "readonly",
"poke16": "readonly",
"poke32": "readonly",
"poke8": "readonly",
"print": "readonly",
"require": "readonly",
"reset": "readonly",
"save": "readonly",
"Serial1": "readonly",
"setBusyIndicator": "readonly",
"setInterval": "readonly",
"setSleepIndicator": "readonly",
"setTime": "readonly",
"setTimeout": "readonly",
"setWatch": "readonly",
"shiftOut": "readonly",
"SPI1": "readonly",
"Terminal": "readonly",
"trace": "readonly",
"VIBRATE": "readonly",
// Aliases and not defined at https://banglejs.com/reference
"g": "readonly",
"WIDGETS": "readonly",
"module": "readonly",
"exports": "writable",
"D0": "readonly",
"D1": "readonly",
"D2": "readonly",
"D3": "readonly",
"D4": "readonly",
"D5": "readonly",
"D6": "readonly",
"D7": "readonly",
"D8": "readonly",
"D9": "readonly",
"D10": "readonly",
"D11": "readonly",
"D12": "readonly",
"D13": "readonly",
"D14": "readonly",
"D15": "readonly",
"D16": "readonly",
"D17": "readonly",
"D18": "readonly",
"D19": "readonly",
"D20": "readonly",
"D21": "readonly",
"D22": "readonly",
"D23": "readonly",
"D24": "readonly",
"D25": "readonly",
"D26": "readonly",
"D27": "readonly",
"D28": "readonly",
"D29": "readonly",
"D30": "readonly",
"D31": "readonly",
"bleServiceOptions": "writable", // available in boot.js code that's called ad part of bootupdate
},
"parserOptions": {
"ecmaVersion": 11
},
"rules": {
"indent": [
"off",
2,
{
"SwitchCase": 1
}
],
"no-constant-condition": "off",
"no-delete-var": "off",
"no-empty": ["warn", { "allowEmptyCatch": true }],
"no-global-assign": "off",
"no-inner-declarations": "off",
"no-prototype-builtins": "off",
"no-redeclare": "off",
"no-unreachable": "warn",
"no-cond-assign": "warn",
"no-useless-catch": "warn",
"no-undef": "warn",
"no-unused-vars": ["warn", { "args": "none" } ],
"no-useless-escape": "off",
"no-control-regex" : "off"
},
overrides: [
...Object.entries(lintExemptions).map(([filePath, {rules}]) => ({
files: [filePath],
rules: Object.fromEntries(rules.map(rule => [rule, "off"])),
})),
],
}

View File

@ -5,7 +5,7 @@
"description": "A detailed description of my great app",
"icon": "app.png",
"tags": "",
"supports" : ["BANGLEJS2"],
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"7chname.app.js","url":"app.js"},

View File

@ -1,44 +0,0 @@
// timeout used to update every minute
var drawTimeout;
// schedule a draw for the next minute
function queueDraw() {
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, 60000 - (Date.now() % 60000));
}
function draw() {
// queue next draw in one minute
queueDraw();
// Work out where to draw...
var x = g.getWidth()/2;
var y = g.getHeight()/2;
g.reset();
// 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("Vector",48);
g.clearRect(0,y-15,g.getWidth(),y+25); // clear the background
g.drawString(timeStr,x,y);
// draw date
y += 35;
g.setFontAlign(0,0).setFont("6x8");
g.clearRect(0,y-4,g.getWidth(),y+4); // clear the background
g.drawString(dateStr,x,y);
}
// Clear the screen once, at startup
g.clear();
// draw immediately at first, queue update
draw();
// Show launcher when middle button pressed
Bangle.setUI("clock");
// Load widgets
Bangle.loadWidgets();
Bangle.drawWidgets();

View File

@ -4,6 +4,7 @@
"version":"0.01",
"description": "A detailed description of my clock",
"icon": "icon.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],

View File

@ -9,3 +9,4 @@
0.09: New app screen (instead of showing settings or the alert) and some optimisations
0.10: Add software back button via setUI
0.11: Add setting to unlock screen
0.12: Fix handling that dates can be given as ms since epoch.

View File

@ -29,16 +29,13 @@ exports.loadData = function () {
dismissDate: new Date(1970),
pauseDate: new Date(1970),
},
require("Storage").readJSON("activityreminder.data.json") || {});
require("Storage").readJSON("activityreminder.data.json") || {});
if (typeof (data.stepsDate) == "string")
data.stepsDate = new Date(data.stepsDate);
if (typeof (data.okDate) == "string")
data.okDate = new Date(data.okDate);
if (typeof (data.dismissDate) == "string")
data.dismissDate = new Date(data.dismissDate);
if (typeof (data.pauseDate) == "string")
data.pauseDate = new Date(data.pauseDate);
data.stepsDate = new Date(typeof data.stepsDate === 'string' ? data.stepsDate : data.stepsDate.ms);
data.okDate = new Date(typeof data.okDate === 'string' ? data.okDate : data.okDate.ms);
data.dismissDate = new Date(typeof data.dismissDate === 'string' ? data.dismissDate : data.dismissDate.ms);
data.pauseDate = new Date(typeof data.pauseDate === 'string' ? data.pauseDate : data.pauseDate.ms);
return data;
};

View File

@ -3,10 +3,10 @@
"name": "Activity Reminder",
"shortName":"Activity Reminder",
"description": "A reminder to take short walks for the ones with a sedentary lifestyle",
"version":"0.11",
"version":"0.12",
"icon": "app.png",
"type": "app",
"tags": "tool,activity",
"tags": "tool,activity,health",
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [

View File

@ -2,4 +2,5 @@
0.02: Remove un-needed fonts to improve memory usage
0.03: Tell clock widgets to hide.
0.04: Swipe down to see widgets, step counter now just uses getHealthStatus
0.05: Report latest HRM rather than HRM 10 minutes ago (fix #2395)
0.05: Report latest HRM rather than HRM 10 minutes ago (fix #2395)
0.06: Use watch temperature

View File

@ -3,8 +3,7 @@
<img src="https://user-images.githubusercontent.com/2981891/175355586-1dfc0d66-6555-4385-b124-1605fdb71a11.jpg" width="250" />
An over-engineered clock inspired by Casio watches.<br/>
It has a dedicated timer, a scratchpad and can display the weather condition 4 days ahead.<br/>
It uses a <a target="_blank" href="https://dotgreg.github.io/advCasioBangleClock/">custom web app</a> to update its content.<br/>
It has a dedicated timer, a scratchpad and displays the current temperature.<br/>
Forked from the awesome Cassio Watch.<br/>
## Todo
@ -21,7 +20,7 @@ Forked from the awesome Cassio Watch.<br/>
- Footsteps
- Battery
- Simple Timer embedded
- Weather forecast (7 days)
- Current temperature
- Scratchpad
## Screenshots
@ -36,14 +35,6 @@ Web interface to update weather & scratchpad <br/>
<img src="https://user-images.githubusercontent.com/2981891/175519121-851bb209-7192-40db-a014-490c344f7597.jpg" width="250" />
## Usage
### How to update the tasks list / weather
- you will need a <a target="_blank" href="https://openweathermap.org/price#weather">free openweathermap.org api key</a>.
- go to https://dotgreg.github.io/advCasioBangleClock/
- Alternatively you can install it on your own server/heroku/service/github pages, the web-app code is <a target="_blank" href="https://github.com/dotgreg/advCasioBangleClock/tree/master/web-app">here</a>
- fill the location and the api key (it will be saved on your browser, no need to do it each time)
- edit the scratchpad with what you want
- click on sync
- reload your clock!
### How to start/stop the timer
- swipe up : add time (+5min)

View File

@ -88,9 +88,9 @@ function drawRocket() {
function getTemperature(){
try {
var weatherJson = storage.readJSON('weather.json');
var weather = weatherJson.weather;
return Math.round(weather.temp-273.15);
var temperature = E.getTemperature()
var formatted = require("locale").temp(temperature).replace(/[^\d-]/g, '');
return formatted;
} catch(ex) {
print(ex)

View File

@ -1,8 +1,8 @@
{ "id": "advcasio",
"name": "Advanced Casio Clock",
"shortName":"advcasio",
"version":"0.05",
"description": "An over-engineered clock inspired by Casio watches. It has a 4 days weather, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.",
"version":"0.06",
"description": "An over-engineered clock inspired by Casio watches. It has current temperature, a timer using swipe and a scratchpad. Can be updated using a dedicated webapp.",
"icon": "app.png",
"tags": "clock",
"type": "clock",
@ -18,8 +18,5 @@
"storage": [
{"name":"advcasio.app.js","url":"app.js"},
{"name":"advcasio.img","url":"app-icon.js","evaluate":true}
],
"data": [
{ "name": "advcasio.data.json", "url": "data.json", "storageFile": true }
]
}

View File

@ -49,3 +49,5 @@
0.44: Add "delete timer after expiration" setting to events.
0.45: Fix new alarm when selectedAlarm is undefined
0.46: Show alarm groups if the Show Group setting is ON. Scroll alarms menu back to previous position when getting back to it.
0.47: Fix wrap around when snoozed through midnight
0.48: Use datetimeinput for Events, if available. Scroll back when getting out of group. Menu date format setting for shorter dates on current year.

View File

@ -2,7 +2,7 @@
This app allows you to add/modify any alarms, timers and events.
Optional: When a keyboard app is detected, you can add a message to display when any of these is triggered.
Optional: When a keyboard app is detected, you can add a message to display when any of these is triggered. If a datetime input app (e.g. datetime_picker) is detected, it will be used for the selection of the date+time of events.
It uses the [`sched` library](https://github.com/espruino/BangleApps/blob/master/apps/sched) to handle the alarm scheduling in an efficient way that can work alongside other apps.

View File

@ -28,6 +28,8 @@ const iconTimerOff = "\0" + (g.theme.dark
// An array of alarm objects (see sched/README.md)
var alarms = require("sched").getAlarms();
// Fix possible wrap around in existing alarms #3281, broken alarms still needs to be saved to get fixed
alarms.forEach(e => e.t %= 86400000); // This can probably be removed in the future when we are sure there are no more broken alarms
function handleFirstDayOfWeek(dow) {
if (firstDayOfWeek == 1) {
@ -48,13 +50,17 @@ function handleFirstDayOfWeek(dow) {
alarms.filter(e => e.timer === undefined).forEach(a => a.dow = handleFirstDayOfWeek(a.dow));
function getLabel(e) {
const dateStr = e.date && require("locale").date(new Date(e.date), 1);
const dateStr = getDateText(e.date);
return (e.timer
? require("time_utils").formatDuration(e.timer)
: (dateStr ? `${dateStr}${e.rp?"*":""} ${require("time_utils").formatTime(e.t)}` : require("time_utils").formatTime(e.t) + (e.rp ? ` ${decodeRepeat(e)}` : ""))
) + (e.msg ? ` ${e.msg}` : "");
}
function getDateText(d) {
return d && (settings.menuDateFormat === "mmdd" ? d.substring(d.startsWith(new Date().getFullYear()) ? 5 : 0) : require("locale").date(new Date(d), 1));
}
function trimLabel(label, maxLength) {
if(settings.showOverflow) return label;
return (label.length > maxLength
@ -73,10 +79,10 @@ function formatAlarmProperty(msg) {
}
}
function showMainMenu(scroll, group) {
function showMainMenu(scroll, group, scrollback) {
const menu = {
"": { "title": group || /*LANG*/"Alarms & Timers", scroll: scroll },
"< Back": () => group ? showMainMenu() : load(),
"< Back": () => group ? showMainMenu(scrollback) : load(),
/*LANG*/"New...": () => showNewMenu(group)
};
const getGroups = settings.showGroup && !group;
@ -96,7 +102,7 @@ function showMainMenu(scroll, group) {
});
if (!group) {
Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g));
Object.keys(groups).sort().forEach(g => menu[g] = () => showMainMenu(null, g, scroller.scroll));
menu[/*LANG*/"Advanced"] = () => showAdvancedMenu();
}
@ -136,6 +142,8 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
var title = date ? (isNew ? /*LANG*/"New Event" : /*LANG*/"Edit Event") : (isNew ? /*LANG*/"New Alarm" : /*LANG*/"Edit Alarm");
var keyboard = "textinput";
try {keyboard = require(keyboard);} catch(e) {keyboard = null;}
var datetimeinput;
try {datetimeinput = require("datetimeinput");} catch(e) {datetimeinput = null;}
const menu = {
"": { "title": title },
@ -143,41 +151,66 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
prepareAlarmForSave(alarm, alarmIndex, time, date);
saveAndReload();
showMainMenu(scroll, group);
},
/*LANG*/"Hour": {
value: time.h,
format: v => ("0" + v).substr(-2),
min: 0,
max: 23,
wrap: true,
onchange: v => time.h = v
},
/*LANG*/"Minute": {
value: time.m,
format: v => ("0" + v).substr(-2),
min: 0,
max: 59,
wrap: true,
onchange: v => time.m = v
},
/*LANG*/"Day": {
value: date ? date.getDate() : null,
min: 1,
max: 31,
wrap: true,
onchange: v => date.setDate(v)
},
/*LANG*/"Month": {
value: date ? date.getMonth() + 1 : null,
format: v => require("date_utils").month(v),
onchange: v => date.setMonth((v+11)%12)
},
/*LANG*/"Year": {
value: date ? date.getFullYear() : null,
min: new Date().getFullYear(),
max: 2100,
onchange: v => date.setFullYear(v)
},
}
};
if (alarm.date && datetimeinput) {
menu[`${getDateText(date.toLocalISOString().slice(0,10))} ${require("time_utils").formatTime(time)}`] = {
value: date,
format: v => "",
onchange: v => {
setTimeout(() => {
var datetime = new Date(v.getTime());
datetime.setHours(time.h, time.m);
datetimeinput.input({datetime}).then(result => {
time.h = result.getHours();
time.m = result.getMinutes();
prepareAlarmForSave(alarm, alarmIndex, time, result, true);
setTimeout(showEditAlarmMenu, 10, alarm, alarmIndex, withDate, scroll, group);
});
}, 100);
}
};
} else {
Object.assign(menu, {
/*LANG*/"Hour": {
value: time.h,
format: v => ("0" + v).substr(-2),
min: 0,
max: 23,
wrap: true,
onchange: v => time.h = v
},
/*LANG*/"Minute": {
value: time.m,
format: v => ("0" + v).substr(-2),
min: 0,
max: 59,
wrap: true,
onchange: v => time.m = v
},
/*LANG*/"Day": {
value: date ? date.getDate() : null,
min: 1,
max: 31,
wrap: true,
onchange: v => date.setDate(v)
},
/*LANG*/"Month": {
value: date ? date.getMonth() + 1 : null,
format: v => require("date_utils").month(v),
onchange: v => date.setMonth((v+11)%12)
},
/*LANG*/"Year": {
value: date ? date.getFullYear() : null,
min: new Date().getFullYear(),
max: 2100,
onchange: v => date.setFullYear(v)
}
});
}
Object.assign(menu, {
/*LANG*/"Message": {
value: alarm.msg,
format: formatAlarmProperty,
@ -239,7 +272,7 @@ function showEditAlarmMenu(selectedAlarm, alarmIndex, withDate, scroll, group) {
saveAndReload();
showMainMenu(scroll, group);
}
};
});
if (!keyboard) delete menu[/*LANG*/"Message"];
if (!keyboard || !settings.showGroup) delete menu[/*LANG*/"Group"];
@ -497,7 +530,7 @@ function showEditTimerMenu(selectedTimer, timerIndex) {
function prepareTimerForSave(timer, timerIndex, time, temp) {
timer.timer = require("time_utils").encodeTime(time);
timer.t = require("time_utils").getCurrentTimeMillis() + timer.timer;
timer.t = (require("time_utils").getCurrentTimeMillis() + timer.timer) % 86400000;
timer.last = 0;
if (!temp) {

View File

@ -2,7 +2,7 @@
"id": "alarm",
"name": "Alarms & Timers",
"shortName": "Alarms",
"version": "0.46",
"version": "0.48",
"description": "Set alarms and timers on your Bangle",
"icon": "app.png",
"tags": "tool,alarm",

View File

@ -1,4 +1,7 @@
0.01: Release
0.02: Rename app
0.03: Add type "clock"
0.04: changed update cylce, when locked
0.04: Changed update cylce, when locked
0.05: Fix support for dark theme + support widgets +
add settings for widgets, order of drawing and hour hand length
0.06: Fix issue showing widgets when app is fast-loaded into from launcher with widgets disabled

View File

@ -1,10 +1,16 @@
# Analog Clock
# Dark Analog Clock
## Features
* second hand
* second hand (only on unlocked screen)
* date
* battery percantage
* no widgets
* battery percentage (showing charge status with color)
* turned off or swipeable widgets (choose in settings)
![logo](andark_screen.png)
## Settings
* whether to load widgets, or not; if widgets are loaded, they are swipeable from the top; if not, NO ACTIONS of widgets are available
* date and battery can be printed both below hands (as if hands were physical) and above (more readable)
* hour hand can be made slighly shorter to improve readability when minute hand is behind a number

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -1,22 +1,43 @@
const defaultSettings = {
loadWidgets : false,
textAboveHands : false,
shortHrHand : false
};
const settings = Object.assign(defaultSettings, require('Storage').readJSON('andark.json',1)||{});
const c={"x":g.getWidth()/2,"y":g.getHeight()/2};
let zahlpos=[];
const zahlpos=(function() {
let z=[];
let sk=1;
for(let i=-10;i<50;i+=5){
let win=i*2*Math.PI/60;
let xsk =c.x+2+Math.cos(win)*(c.x-10),
ysk =c.y+2+Math.sin(win)*(c.x-10);
if(sk==3){xsk-=10;}
if(sk==6){ysk-=10;}
if(sk==9){xsk+=10;}
if(sk==12){ysk+=10;}
if(sk==10){xsk+=3;}
z.push([sk,xsk,ysk]);
sk+=1;
}
return z;
})();
let unlock = false;
function zeiger(len,dia,tim){
const x =c.x+ Math.cos(tim)*len/2,
y =c.y + Math.sin(tim)*len/2,
const x=c.x+ Math.cos(tim)*len/2,
y=c.y + Math.sin(tim)*len/2,
d={"d":3,"x":dia/2*Math.cos(tim+Math.PI/2),"y":dia/2*Math.sin(tim+Math.PI/2)},
pol=[c.x-d.x,c.y-d.y,c.x+d.x,c.y+d.y,x+d.x,y+d.y,x-d.x,y-d.y];
return pol;
}
function draw(){
const d=new Date();
function drawHands(d) {
let m=d.getMinutes(), h=d.getHours(), s=d.getSeconds();
//draw black rectangle in the middle to clear screen from scale and hands
g.setColor(0,0,0);
g.fillRect(10,10,2*c.x-10,2*c.x-10);
g.setColor(1,1,1);
if(h>12){
@ -29,30 +50,64 @@ function draw(){
m=2*Math.PI/60*(m)-Math.PI/2;
s=2*Math.PI/60*s-Math.PI/2;
g.setFontAlign(0,0);
g.setFont("Vector",10);
let dateStr = " "+require("locale").date(d)+" ";
g.drawString(dateStr, c.x, c.y+20, true);
// g.drawString(d.getDate(),1.4*c.x,c.y,true);
g.drawString(Math.round(E.getBattery()/5)*5+"%",c.x,c.y+40,true);
drawlet();
//g.setColor(1,0,0);
const hz = zeiger(100,5,h);
const hz = zeiger(settings.shortHrHand?88:100,5,h);
g.fillPoly(hz,true);
// g.setColor(1,1,1);
//g.setColor(1,1,1);
const minz = zeiger(150,5,m);
g.fillPoly(minz,true);
if (unlock){
const sekz = zeiger(150,2,s);
g.fillPoly(sekz,true);
const sekz = zeiger(150,2,s);
g.fillPoly(sekz,true);
}
g.fillCircle(c.x,c.y,4);
}
function drawText(d) {
g.setFont("Vector",10);
g.setBgColor(0,0,0);
g.setColor(1,1,1);
let dateStr = require("locale").date(d);
g.drawString(dateStr, c.x, c.y+20, true);
let batStr = Math.round(E.getBattery()/5)*5+"%";
if (Bangle.isCharging()) {
g.setBgColor(1,0,0);
}
g.drawString(batStr, c.x, c.y+40, true);
}
function drawNumbers() {
//draws the numbers on the screen
g.setFont("Vector",20);
g.setColor(1,1,1);
g.setBgColor(0,0,0);
for(let i = 0;i<12;i++){
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2],true);
}
}
function draw(){
// draw black rectangle in the middle to clear screen from scale and hands
g.setColor(0,0,0);
g.fillRect(10,10,2*c.x-10,2*c.x-10);
// prepare for drawing the text
g.setFontAlign(0,0);
// do drawing
drawNumbers();
const d=new Date();
if (settings.textAboveHands) {
drawHands(d); drawText(d);
} else {
drawText(d); drawHands(d);
}
}
//draws the scale once the app is startet
function drawScale(){
// clear the screen
g.setBgColor(0,0,0);
g.clear();
// draw the ticks of the scale
for(let i=-14;i<47;i++){
const win=i*2*Math.PI/60;
let d=2;
@ -64,61 +119,34 @@ function drawScale(){
}
}
//draws the numbers on the screen
//// main running sequence ////
function drawlet(){
g.setFont("Vector",20);
for(let i = 0;i<12;i++){
g.drawString(zahlpos[i][0],zahlpos[i][1],zahlpos[i][2]);
}
}
//calcultes the Position of the numbers when app starts and saves them in an array
function setlet(){
let sk=1;
for(let i=-10;i<50;i+=5){
let win=i*2*Math.PI/60;
let xsk =c.x+2+Math.cos(win)*(c.x-10),
ysk =c.y+2+Math.sin(win)*(c.x-10);
if(sk==3){xsk-=10;}
if(sk==6){ysk-=10;}
if(sk==9){xsk+=10;}
if(sk==12){ysk+=10;}
if(sk==10){xsk+=3;}
zahlpos.push([sk,xsk,ysk]);
sk+=1;
}
}
setlet();
// Show launcher when middle button pressed, and widgets that we're clock
Bangle.setUI("clock");
// Load widgets if needed, and make them show swipeable
if (settings.loadWidgets) {
Bangle.loadWidgets();
require("widget_utils").swipeOn();
} else if (global.WIDGETS) require("widget_utils").hide();
// Clear the screen once, at startup
g.setBgColor(0,0,0);
g.clear();
drawScale();
draw();
let secondInterval= setInterval(draw, 1000);
// Stop updates when LCD is off, restart when on
let secondInterval = setInterval(draw, 1000);
// Stop updates when LCD is off, restart when on
Bangle.on('lcdPower',on=>{
if (secondInterval) clearInterval(secondInterval);
secondInterval = undefined;
if (on) {
secondInterval = setInterval(draw, 1000);
draw(); // draw immediately
secondInterval = setInterval(draw, 1000);
draw(); // draw immediately
}
});
Bangle.on('lock',on=>{
unlock = !on;
if (secondInterval) clearInterval(secondInterval);
secondInterval = undefined;
if (!on) {
secondInterval = setInterval(draw, 1000);
unlock = true;
draw(); // draw immediately
}else{
secondInterval = setInterval(draw, 60000);
unlock = false;
draw();
}
});
// Show launcher when middle button pressed
Bangle.setUI("clock");
secondInterval = setInterval(draw, unlock ? 1000 : 60000);
draw(); // draw immediately
});
Bangle.on('charging',on=>{draw();});

View File

@ -1,15 +1,18 @@
{ "id": "andark",
"name": "Analog Dark",
"shortName":"AnDark",
"version":"0.04",
"version":"0.06",
"description": "analog clock face without disturbing widgets",
"icon": "andark_icon.png",
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"screenshots": [{"url":"andark_screen.png"}],
"readme": "README.md",
"storage": [
{"name":"andark.app.js","url":"app.js"},
{"name":"andark.settings.js","url":"settings.js"},
{"name":"andark.img","url":"app_icon.js","evaluate":true}
]
],
"data": [{"name":"andark.json"}]
}

View File

@ -34,3 +34,6 @@
0.32: Added support for loyalty cards from gadgetbridge
0.33: Fix alarms created in Gadgetbridge not repeating
0.34: Implement API for activity tracks fetching (Recorder app logs).
0.35: Implement API to enable/disable acceleration data tracking.
0.36: Move from wrapper function to {} and let - faster execution at boot
Allow `calendar-` to take an array of items to remove

View File

@ -44,6 +44,10 @@ The boot code also provides some useful functions:
* `id` - a custom (string) ID
* `timeout` - a timeout for the request in milliseconds (default 30000ms)
* `xpath` an xPath query to run on the request (but right now the URL requested must be XML - HTML is rarely XML compliant)
* `return` for xpath, if not specified, one result is returned. If `return:"array"` an array of results is returned.
* `method` HTTP method (default is `get`) - `get/post/head/put/patch/delete`
* `body` the body of the HTTP request
* `headers` an object of headers, eg `{HeaderOne : "headercontents"}`
eg:

View File

@ -1,21 +1,22 @@
(function() {
function gbSend(message) {
/* global GB */
{
let gbSend = function(message) {
Bluetooth.println("");
Bluetooth.println(JSON.stringify(message));
}
var lastMsg; // for music messages - may not be needed now...
var actInterval; // Realtime activity reporting interval when `act` is true
var actHRMHandler; // For Realtime activity reporting
var gpsState = {}; // keep information on GPS via Gadgetbridge
let lastMsg; // for music messages - may not be needed now...
let actInterval; // Realtime activity reporting interval when `act` is true
let actHRMHandler; // For Realtime activity reporting
let gpsState = {}; // keep information on GPS via Gadgetbridge
// this settings var is deleted after this executes to save memory
var settings = require("Storage").readJSON("android.settings.json",1)||{};
let settings = require("Storage").readJSON("android.settings.json",1)||{};
//default alarm settings
if (settings.rp == undefined) settings.rp = true;
if (settings.as == undefined) settings.as = true;
if (settings.vibrate == undefined) settings.vibrate = "..";
require('Storage').writeJSON("android.settings.json", settings);
var _GB = global.GB;
let _GB = global.GB;
let fetchRecInterval;
global.GB = (event) => {
// feed a copy to other handlers if there were any
@ -121,7 +122,10 @@
var cal = require("Storage").readJSON("android.calendar.json",true);
//if any of those happen we are out of sync!
if (!cal || !Array.isArray(cal)) cal = [];
cal = cal.filter(e=>e.id!=event.id);
if (Array.isArray(event.id))
cal = cal.filter(e=>!event.id.includes(e.id));
else
cal = cal.filter(e=>e.id!=event.id);
require("Storage").writeJSON("android.calendar.json", cal);
},
//triggered by GB, send all ids
@ -292,6 +296,10 @@
// we receive all, just override what we have
if (Array.isArray(event.d))
require("Storage").writeJSON("android.cards.json", event.d);
},
"accelsender": function () {
require("Storage").writeJSON("accelsender.json", {enabled: event.enable, interval: event.interval});
load();
}
};
var h = HANDLERS[event.t];
@ -332,7 +340,7 @@
};
// Battery monitor
function sendBattery() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
let sendBattery = function() { gbSend({ t: "status", bat: E.getBattery(), chg: Bangle.isCharging()?1:0 }); }
Bangle.on("charging", sendBattery);
NRF.on("connect", () => setTimeout(function() {
sendBattery();
@ -427,4 +435,4 @@
// remove settings object so it's not taking up RAM
delete settings;
})();
}

View File

@ -2,7 +2,7 @@
"id": "android",
"name": "Android Integration",
"shortName": "Android",
"version": "0.34",
"version": "0.36",
"description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.",
"icon": "app.png",
"tags": "tool,system,messages,notifications,gadgetbridge",

View File

@ -1,153 +0,0 @@
let result = true;
function assertTrue(condition, text) {
if (!condition) {
result = false;
print("FAILURE: " + text);
} else print("OK: " + text);
}
function assertFalse(condition, text) {
assertTrue(!condition, text);
}
function assertUndefinedOrEmpty(array, text) {
assertTrue(!array || array.length == 0, text);
}
function assertNotEmpty(array, text) {
assertTrue(array && array.length > 0, text);
}
let internalOn = () => {
return getPinMode((process.env.HWVERSION==2)?D30:D26) == "input";
};
let sec = {
connected: false
};
NRF.getSecurityStatus = () => sec;
// add an empty starting point to make the asserts work
Bangle._PWR={};
let teststeps = [];
teststeps.push(()=>{
print("Not connected, should use internal GPS");
assertTrue(!NRF.getSecurityStatus().connected, "Not connected");
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
assertFalse(Bangle.isGPSOn(), "isGPSOn");
assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on");
assertNotEmpty(Bangle._PWR.GPS, "GPS");
assertTrue(Bangle.isGPSOn(), "isGPSOn");
assertTrue(internalOn(), "Internal GPS on");
assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
assertFalse(Bangle.isGPSOn(), "isGPSOn");
assertFalse(internalOn(), "Internal GPS off");
});
teststeps.push(()=>{
print("Connected, should use GB GPS");
sec.connected = true;
assertTrue(NRF.getSecurityStatus().connected, "Connected");
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
assertFalse(Bangle.isGPSOn(), "isGPSOn");
assertFalse(internalOn(), "Internal GPS off");
print("Internal GPS stays on until the first GadgetBridge event arrives");
assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on");
assertNotEmpty(Bangle._PWR.GPS, "GPS");
assertTrue(Bangle.isGPSOn(), "isGPSOn");
assertTrue(internalOn(), "Internal GPS on");
print("Send minimal GadgetBridge GPS event to trigger switch");
GB({t:"gps"});
});
teststeps.push(()=>{
print("GPS should be on, internal off");
assertNotEmpty(Bangle._PWR.GPS, "GPS");
assertTrue(Bangle.isGPSOn(), "isGPSOn");
assertFalse(internalOn(), "Internal GPS off");
});
teststeps.push(()=>{
print("Switching GPS off turns both GadgetBridge as well as internal off");
assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
assertUndefinedOrEmpty(Bangle._PWR.GPS, "No GPS");
assertFalse(Bangle.isGPSOn(), "isGPSOn");
assertFalse(internalOn(), "Internal GPS off");
});
teststeps.push(()=>{
print("Wait for all timeouts to run out");
return 12000;
});
teststeps.push(()=>{
print("Check auto switch when no GPS event arrives");
assertTrue(Bangle.setGPSPower(1, "test"), "Switch GPS on");
assertNotEmpty(Bangle._PWR.GPS, "GPS");
assertTrue(Bangle.isGPSOn(), "isGPSOn");
assertTrue(internalOn(), "Internal GPS on");
print("Send minimal GadgetBridge GPS event to trigger switch");
GB({t:"gps"});
print("Internal should be switched off now");
assertNotEmpty(Bangle._PWR.GPS, "GPS");
assertTrue(Bangle.isGPSOn(), "isGPSOn");
assertFalse(internalOn(), "Internal GPS off");
//wait on next test
return 12000;
});
teststeps.push(()=>{
print("Check state and disable GPS, internal should be on");
assertNotEmpty(Bangle._PWR.GPS, "GPS");
assertTrue(Bangle.isGPSOn(), "isGPSOn");
assertTrue(internalOn(), "Internal GPS on");
assertFalse(Bangle.setGPSPower(0, "test"), "Switch GPS off");
});
teststeps.push(()=>{
print("Result Overall is " + (result ? "OK" : "FAIL"));
});
let wrap = (functions) => {
if (functions.length > 0) {
setTimeout(()=>{
let waitingTime = functions.shift()();
if (waitingTime){
print("WAITING: ", waitingTime);
setTimeout(()=>{wrap(functions);}, waitingTime);
} else
wrap(functions);
},0);
}
};
setTimeout(()=>{
wrap(teststeps);
}, 5000);

View File

@ -6,3 +6,4 @@
0.06: Fix azimuth (bug #2651), only show degrees
0.07: Minor code improvements
0.08: Minor code improvements
0.09: Fix: Handle when the moon rise/set do not occur on the current day

View File

@ -180,8 +180,8 @@ function drawMoonTimesPage(gps, title) {
const moonColor = g.theme.dark ? {r: 1, g: 1, b: 1} : {r: 0, g: 0, b: 0};
const pageData = {
Rise: dateToTimeString(times.rise),
Set: dateToTimeString(times.set),
Rise: times.rise ? dateToTimeString(times.rise) : "Not today",
Set: times.set ? dateToTimeString(times.set) : "Not today",
};
drawData(title, pageData, null, g.getHeight()/2 - Object.keys(pageData).length/2*20 + 5);
@ -240,7 +240,7 @@ function sunIndexPageMenu(gps) {
"title": "-- Sun --",
},
"Current Pos": () => {
m = E.showMenu();
E.showMenu();
drawSunShowPage(gps, "Current Pos", new Date());
},
};
@ -248,13 +248,13 @@ function sunIndexPageMenu(gps) {
Object.keys(sunTimes).sort().reduce((menu, key) => {
const title = titlizeKey(key);
menu[title] = () => {
m = E.showMenu();
E.showMenu();
drawSunShowPage(gps, key, sunTimes[key]);
};
return menu;
}, sunMenu);
sunMenu["< Back"] = () => m = indexPageMenu(gps);
sunMenu["< Back"] = () => indexPageMenu(gps);
return E.showMenu(sunMenu);
}
@ -266,18 +266,18 @@ function moonIndexPageMenu(gps) {
"title": "-- Moon --",
},
"Times": () => {
m = E.showMenu();
E.showMenu();
drawMoonTimesPage(gps, /*LANG*/"Times");
},
"Position": () => {
m = E.showMenu();
E.showMenu();
drawMoonPositionPage(gps, /*LANG*/"Position");
},
"Illumination": () => {
m = E.showMenu();
E.showMenu();
drawMoonIlluminationPage(gps, /*LANG*/"Illumination");
},
"< Back": () => m = indexPageMenu(gps),
"< Back": () => indexPageMenu(gps),
};
return E.showMenu(moonMenu);
@ -289,10 +289,10 @@ function indexPageMenu(gps) {
"title": /*LANG*/"Select",
},
/*LANG*/"Sun": () => {
m = sunIndexPageMenu(gps);
sunIndexPageMenu(gps);
},
/*LANG*/"Moon": () => {
m = moonIndexPageMenu(gps);
moonIndexPageMenu(gps);
},
"< Back": () => { load(); }
};
@ -300,9 +300,9 @@ function indexPageMenu(gps) {
return E.showMenu(menu);
}
function getCenterStringX(str) {
return (g.getWidth() - g.stringWidth(str)) / 2;
}
//function getCenterStringX(str) {
// return (g.getWidth() - g.stringWidth(str)) / 2;
//}
function init() {
let location = require("Storage").readJSON("mylocation.json",1)||{"lat":51.5072,"lon":0.1276,"location":"London"};
@ -311,5 +311,4 @@ function init() {
Bangle.drawWidgets();
}
let m;
init();

View File

@ -1,7 +1,7 @@
{
"id": "astrocalc",
"name": "Astrocalc",
"version": "0.08",
"version": "0.09",
"description": "Calculates interesting information on the sun like sunset and sunrise and moon cycles for the current day based on your location from MyLocation app",
"icon": "astrocalc.png",
"tags": "app,sun,moon,cycles,tool,outdoors",

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Add black- and whitelist for apps. Configure the timout time.

View File

@ -6,11 +6,11 @@ Sets a timeout to load the clock face. The timeout is stopped and started again
Install with app loader and Auto Reset will run in background. If you don't interact with the watch it will time out to the clock face after 10 minutes.
Through the settings apps can be black-/whitelisted and the timeout length can be configured.
## TODO
- Add settings page
- set how many minutes the timeout should count down.
- whitelist/blacklist for apps.
- per app specific timeout lengths?
## Requests

View File

@ -1,12 +1,29 @@
{
const DEFAULTS = {
mode: 0,
apps: [],
timeout: 10
};
const settings = require("Storage").readJSON("autoreset.json", 1) || DEFAULTS;
// Check if the back button should be enabled for the current app.
// app is the src file of the app.
// Derivative of the backswipe app's logic.
function enabledForApp(app) {
if (Bangle.CLOCK==1) return false;
if (!settings) return true;
let isListed = settings.apps.filter((a) => a.files.includes(app)).length > 0;
return settings.mode===0?!isListed:isListed;
}
let timeoutAutoreset;
let resetTimeoutAutoreset = (force)=>{
const resetTimeoutAutoreset = (force)=>{
if (timeoutAutoreset) clearTimeout(timeoutAutoreset);
setTimeout(()=>{ // Short outer timeout to make sure we have time to leave clock face before checking `Bangle.CLOCK!=1`.
if (Bangle.CLOCK!=1) { // Only add timeout if not already on clock face.
if (enabledForApp(global.__FILE__)) {
timeoutAutoreset = setTimeout(()=>{
if (Bangle.CLOCK!=1) Bangle.showClock();
}, 10*60*1000);
}, settings.timeout*60*1000);
}
},200);
};

View File

@ -1,6 +1,6 @@
{ "id": "autoreset",
"name": "Auto Reset",
"version":"0.01",
"version":"0.02",
"description": "Sets a timeout to load the clock face. The timeout is stopped and started again upon user input.",
"icon": "app.png",
"type": "bootloader",
@ -8,6 +8,10 @@
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"autoreset.boot.js","url":"boot.js"}
{"name":"autoreset.boot.js","url":"boot.js"},
{"name":"autoreset.settings.js","url":"settings.js"}
],
"data":[
{"name":"autoreset.json"}
]
}

View File

@ -1,3 +1,6 @@
0.01: New App!
0.02: Don't fire if the app uses swipes already.
0.03: Only count defined handlers in the handler array.
0.04: Fix messages auto opened by `messagegui` could not be blacklisted. Needs
a refresh by deselecting and reselecting the "Messages" app throught Back Swipe
settings.

View File

@ -47,9 +47,9 @@
function enabledForApp(app) {
if (!settings) return true;
if (settings.mode === 0) {
return !(settings.apps.filter((a) => a.src === app).length > 0);
return !(settings.apps.filter((a) => (a.src===app)||(a.files&&a.files.includes(app))).length > 0); // The `a.src===app` and `a.files&&...` checks are for backwards compatibility. Otherwise only `a.files.includes(app)` is needed.
} else if (settings.mode === 1) {
return settings.apps.filter((a) => a.src === app).length > 0;
return settings.apps.filter((a) => (a.src===app)||(a.files&&a.files.includes(app))).length > 0;
} else {
return settings.mode === 2 ? true : false;
}

View File

@ -1,7 +1,7 @@
{ "id": "backswipe",
"name": "Back Swipe",
"shortName":"BackSwipe",
"version":"0.03",
"version":"0.04",
"description": "Service that allows you to use an app's back button using left to right swipe gesture",
"icon": "app.png",
"tags": "back,gesture,swipe",

View File

@ -26,7 +26,8 @@
return appInfo && {
'name': appInfo.name,
'sortorder': appInfo.sortorder,
'src': appInfo.src
'src': appInfo.src,
'files': appInfo.files
};
}).filter(app => app && !!app.src);
apps.sort((a, b) => {

View File

@ -6,3 +6,4 @@
0.06: Tell clock widgets to hide.
0.07: Better battery graphic - now has green, yellow and red sections; battery status reflected in the bar across the middle of the screen; current battery state checked only once every 15 minutes, leading to longer-lasting battery charge
0.08: Minor code improvements
0.09: Something was changing the value of the "width" variable, which caused the battery usage feature to malfunction. The "width" variable has been renamed - the cause remains a mystery.

View File

@ -12,7 +12,7 @@ Graphics.prototype.setFontOpenSans = function(scale) {
var drawTimeout;
var lastBattCheck = 0;
var width = 0;
var batteryUsedWidth = 0;
function queueDraw(millis_now) {
if (drawTimeout) clearTimeout(drawTimeout);
@ -32,8 +32,8 @@ function draw() {
if ((date.getTime() >= lastBattCheck + 15*60000) || Bangle.isCharging()) {
lastBattCheck = date.getTime();
width = E.getBattery();
width += width/2;
batteryUsedWidth = E.getBattery();
batteryUsedWidth += batteryUsedWidth/2;
}
g.reset();
@ -58,7 +58,7 @@ function draw() {
g.fillRect(167,163,170,167);
if (Bangle.isCharging()) {
g.setColor(1,1,0);
g.fillRect(12,162,12+width,168);
g.fillRect(12,162,12+batteryUsedWidth,168);
} else {
g.setColor(1,0,0);
g.fillRect(12,162,57,168);
@ -67,16 +67,16 @@ function draw() {
g.setColor(0,1,0);
g.fillRect(73,162,162,168);
}
if (width < 150) {
if (batteryUsedWidth < 150) {
g.setColor(g.theme.bg);
g.fillRect(12+width+1,162,162,168);
g.fillRect(12+batteryUsedWidth+1,162,162,168);
}
if (Bangle.isCharging()) {
g.setColor(1,1,0);
} else if (width <= 45) {
} else if (batteryUsedWidth <= 45) {
g.setColor(1,0,0);
} else if (width <= 60) {
} else if (batteryUsedWidth <= 60) {
g.setColor(1,1,0);
} else {
g.setColor(0, 1, 0);

View File

@ -1,7 +1,7 @@
{ "id": "bigdclock",
"name": "Big digit clock containing just the essentials",
"shortName":"Big digit clk",
"version": "0.08",
"version": "0.09",
"description": "A clock containing just the essentials, made as easy to read as possible for those of us that need glasses. It contains the time, the day-of-week, the day-of-month, and the current battery state-of-charge.",
"icon": "bigdclock.png",
"type": "clock",

View File

@ -3,3 +3,10 @@
0.03: Added setting for fullscreen option
0.04: Added settings to hide unused squares and show date
0.05: Minor code improvements
0.06: Added setting to show battery and added artwork to date
0.07: Removed percentage from battery and cleaned up logic
0.08: Changed month to day and text color to black on date
0.09: Changed day color back to white
0.10: Add blinking when charging
0.11: Changed battery to buzz instead of blink and fixed battery counter
0.12: Got rid of battery counter

View File

@ -1,13 +1,50 @@
var settings = Object.assign({
fullscreen: false,
fullscreen: true,
hidesq: false,
showdate: false,
showdate: true,
showbat: true,
}, require('Storage').readJSON("binaryclk.json", true) || {});
function draw() {
var gap = 4;
var mgn = 24;
var sq = 33;
if (settings.fullscreen) {
gap = 8;
mgn = 0;
sq = 34;
}
var pos = sq + gap;
function drawbat() {
var bat = E.getBattery();
if (bat < 20) {
g.setColor('#FF0000');
} else if (bat < 40) {
g.setColor('#FFA500');
} else {
g.setColor('#00FF00');
}
g.fillRect(Math.floor(mgn/2) + gap + 2 * pos, mgn + gap, Math.floor(mgn/2) + gap + 2 * pos + Math.floor(bat * sq / 100), mgn + gap + sq);
}
function drawbatrect() {
if (g.theme.dark) {
g.setColor(-1);
} else {
g.setColor(1);
}
g.drawRect(Math.floor(mgn/2) + gap + 2 * pos, mgn + gap, Math.floor(mgn/2) + gap + 2 * pos + sq, mgn + gap + sq);
}
function draw() {
let i = 0;
var dt = new Date();
var h = dt.getHours(), m = dt.getMinutes(), d = dt.getDate();
var h = dt.getHours();
var m = dt.getMinutes();
var d = dt.getDate();
var day = dt.toString().substring(0,3);
const t = [];
t[0] = Math.floor(h/10);
@ -18,18 +55,6 @@ function draw() {
g.reset();
g.clearRect(Bangle.appRect);
let i = 0;
var gap = 8;
var mgn = 20;
if (settings.fullscreen) {
gap = 12;
mgn = 0;
}
const sq = 29;
var pos = sq + gap;
for (let r = 3; r >= 0; r--) {
for (let c = 0; c < 4; c++) {
if (t[c] & Math.pow(2, r)) {
@ -47,25 +72,43 @@ function draw() {
if (settings.hidesq) {
c1sqhide = 2;
c3sqhide = 1;
}
if (settings.hidesq) {
g.clearRect(Math.floor(mgn/2), mgn, Math.floor(mgn/2) + pos, mgn + c1sqhide * pos);
g.clearRect(Math.floor(mgn/2) + 2 * pos + gap, mgn, Math.floor(mgn/2) + 3 * pos, mgn + c3sqhide * pos);
}
if (settings.showdate) {
g.setFontAlign(0, 0);
g.setColor(-1).fillRect(Math.floor(mgn/2) + gap, mgn + gap, Math.floor(mgn/2) + gap + sq, mgn + gap + sq);
g.setColor('#FF0000').fillRect(Math.floor(mgn/2) + gap, mgn + gap, Math.floor(mgn/2) + gap + sq, mgn + gap + 12);
g.setFontAlign(0, -1);
g.setFont("Vector",12);
g.setColor(-1).drawString(day, Math.ceil(mgn/2) + gap + Math.ceil(sq/2) + 1, mgn + gap + 1);
g.setFontAlign(0, 1);
g.setFont("Vector",20);
g.setColor(1).drawString(d, Math.ceil(mgn/2) + gap + Math.ceil(sq/2) + 1, mgn + gap + sq + 2);
if (g.theme.dark) {
g.setColor(-1);
} else {
g.setColor(1);
g.drawLine(Math.floor(mgn/2) + gap, mgn + gap + 13, Math.floor(mgn/2) + gap + sq, mgn + gap + 13);
}
g.drawRect(Math.floor(mgn/2) + gap, mgn + gap, Math.floor(mgn/2) + gap + sq, mgn + gap + sq);
g.drawString(d, Math.ceil(mgn/2) + gap + Math.ceil(sq/2) + 1, mgn + gap + Math.ceil(sq/2) + 1);
}
if (settings.showbat) {
drawbat();
drawbatrect();
}
}
g.clear();
draw();
/*var secondInterval =*/ setInterval(draw, 60000);
setInterval(draw, 60000);
Bangle.setUI("clock");
if (!settings.fullscreen) {
Bangle.loadWidgets();
Bangle.drawWidgets();
}
}
Bangle.on('charging', function(charging) {
if(charging) Bangle.buzz();
});

View File

@ -1,8 +1,8 @@
{
"id": "binaryclk",
"name": "Bin Clock",
"version": "0.05",
"description": "Clock face to show binary time in 24 hour format",
"version": "0.12",
"description": "Binary clock with date and battery",
"icon": "app-icon.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -4,6 +4,7 @@
fullscreen: false,
hidesq: false,
showdate: false,
showbat: false,
}, require('Storage').readJSON(FILE, true) || {});
function writeSettings() {
@ -34,5 +35,12 @@
writeSettings();
},
},
'Show Battery': {
value: settings.showbat,
onchange: v => {
settings.showbat = v;
writeSettings();
},
},
});
})

View File

@ -69,3 +69,6 @@
0.58: "Make Connectable" temporarily bypasses the whitelist
0.59: Whitelist: Try to resolve peer addresses using NRF.resolveAddress() - for 2v19 or 2v18 cutting edge builds
0.60: Minor code improvements
0.61: Instead of breaking execution with an Exception when updating boot, just use if..else (fix 'Uncaught undefined')
0.62: Handle setting for configuring BLE privacy
0.63: Only set BLE `display:1` if we have a passkey

View File

@ -14,15 +14,15 @@ if (DEBUG) {
}
if (require('Storage').hash) { // new in 2v11 - helps ensure files haven't changed
let CRC = E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\.boot\.js/)+E.CRC32(process.env.GIT_COMMIT);
boot += `if (E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
boot += `if(E.CRC32(require('Storage').read('setting.json'))+require('Storage').hash(/\\.boot\\.js/)+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
} else {
let CRC = E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\.boot\.js/))+E.CRC32(process.env.GIT_COMMIT);
boot += `if (E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
boot += `if(E.CRC32(require('Storage').read('setting.json'))+E.CRC32(require('Storage').list(/\\.boot\\.js/))+E.CRC32(process.env.GIT_COMMIT)!=${CRC})`;
}
boot += ` { eval(require('Storage').read('bootupdate.js')); throw "Storage Updated!"}\n`;
boot += `{eval(require('Storage').read('bootupdate.js'));print("Storage Updated!")}else{\n`;
boot += `E.setFlags({pretokenise:1});\n`;
boot += `var bleServices = {}, bleServiceOptions = { uart : true};\n`;
bootPost += `NRF.setServices(bleServices, bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code
bootPost += `NRF.setServices(bleServices,bleServiceOptions);delete bleServices,bleServiceOptions;\n`; // executed after other boot code
if (s.ble!==false) {
if (s.HID) { // Human interface device
if (s.HID=="joy") boot += `Bangle.HID = E.toUint8Array(atob("BQEJBKEBCQGhAAUJGQEpBRUAJQGVBXUBgQKVA3UBgQMFAQkwCTEVgSV/dQiVAoECwMA="));`;
@ -78,7 +78,12 @@ if (global.save) boot += `global.save = function() { throw new Error("You can't
// Apply any settings-specific stuff
if (s.options) boot+=`Bangle.setOptions(${E.toJS(s.options)});\n`;
if (s.brightness && s.brightness!=1) boot+=`Bangle.setLCDBrightness(${s.brightness});\n`;
if (s.passkey!==undefined && s.passkey.length==6) boot+=`NRF.setSecurity({passkey:${E.toJS(s.passkey.toString())}, mitm:1, display:1});\n`;
if (s.bleprivacy || (s.passkey!==undefined && s.passkey.length==6)) {
let passkey = s.passkey ? `passkey:${E.toJS(s.passkey.toString())},display:1,mitm:1,` : "";
let privacy = s.bleprivacy ? `privacy:${E.toJS(s.bleprivacy)},` : "";
boot+=`NRF.setSecurity({${passkey}${privacy}});\n`;
}
if (s.blename === false) boot+=`NRF.setAdvertising({},{showName:false});\n`;
if (s.whitelist && !s.whitelist_disabled) boot+=`NRF.on('connect', function(addr) { if (!NRF.ignoreWhitelist) { let whitelist = (require('Storage').readJSON('setting.json',1)||{}).whitelist; if (NRF.resolveAddress !== undefined) { let resolvedAddr = NRF.resolveAddress(addr); if (resolvedAddr !== undefined) addr = resolvedAddr + " (resolved)"; } if (!whitelist.includes(addr)) NRF.disconnect(); }});\n`;
if (s.rotate) boot+=`g.setRotation(${s.rotate&3},${s.rotate>>2});\n` // screen rotation
// ================================================== FIXING OLDER FIRMWARES
@ -105,7 +110,7 @@ if (!date.toLocalISOString) boot += `Date.prototype.toLocalISOString = function(
// show timings
if (DEBUG) boot += `print(".boot0",0|(Date.now()-_tm),"ms");_tm=Date.now();\n`
// ================================================== BOOT.JS
// Append *.boot.js files.
// Append *.boot.js files.
// Name files with a number - eg 'foo.5.boot.js' to enforce order (lowest first). Numbered files get placed before non-numbered
// These could change bleServices/bleServiceOptions if needed
let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
@ -122,6 +127,7 @@ let bootFiles = require('Storage').list(/\.boot\.js$/).sort((a,b)=>{
return a==b ? 0 : (a>b ? 1 : -1);
});
// precalculate file size
bootPost += "}";
let fileSize = boot.length + bootPost.length;
bootFiles.forEach(bootFile=>{
// match the size of data we're adding below in bootFiles.forEach

View File

@ -1,7 +1,7 @@
{
"id": "boot",
"name": "Bootloader",
"version": "0.60",
"version": "0.63",
"description": "This is needed by Bangle.js to automatically load the clock, menu, widgets and settings",
"icon": "bootloader.png",
"type": "bootloader",

View File

@ -1,2 +1,3 @@
0.01: Initial release.
0.02: Handle the case where other apps have set bleAdvert to an array
0.03: Use the bleAdvert module

View File

@ -1,26 +1,8 @@
(() => {
function advertiseBattery() {
if(Array.isArray(Bangle.bleAdvert)){
// ensure we're in the cycle
var found = false;
for(var ad in Bangle.bleAdvert){
if(ad[0x180F]){
ad[0x180F] = [E.getBattery()];
found = true;
break;
}
}
if(!found)
Bangle.bleAdvert.push({ 0x180F: [E.getBattery()] });
}else{
// simple object
Bangle.bleAdvert[0x180F] = [E.getBattery()];
}
NRF.setAdvertising(Bangle.bleAdvert);
require("ble_advert").set(0x180F, [E.getBattery()]);
}
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
setInterval(advertiseBattery, 60 * 1000);
advertiseBattery();
})();

View File

@ -2,7 +2,7 @@
"id": "bootgattbat",
"name": "BLE GATT Battery Service",
"shortName": "BLE Battery Service",
"version": "0.02",
"version": "0.03",
"description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n",
"icon": "bluetooth.png",
"type": "bootloader",

View File

@ -1,2 +1,4 @@
0.01: Initial release.
0.02: Added compatibility to OpenTracks and added HRM Location
0.02: Added compatibility to OpenTracks and added HRM Location
0.03: Allow setting to keep BLE connected
0.04: Use the bleAdvert module

View File

@ -4,18 +4,13 @@
* This function prepares BLE heart rate Advertisement.
*/
NRF.setAdvertising(
{
0x180d: undefined
},
{
// We need custom Advertisement settings for Apps like OpenTracks
connectable: true,
discoverable: true,
scannable: true,
whenConnected: true,
}
);
require("ble_advert").set(0x180d, undefined, {
// We need custom Advertisement settings for Apps like OpenTracks
connectable: true,
discoverable: true,
scannable: true,
whenConnected: true,
});
NRF.setServices({
0x180D: { // heart_rate
@ -28,8 +23,10 @@
}
}
});
}
const keepConnected = (require("Storage").readJSON("gatthrm.settings.json", 1) || {}).keepConnected;
function updateBLEHeartRate(hrm) {
/*
* Send updated heart rate measurement via BLE
@ -50,13 +47,14 @@
} catch (error) {
if (error.message.includes("BLE restart")) {
/*
* BLE has to restart after service setup.
* BLE has to restart after service setup.
*/
NRF.disconnect();
if(!keepConnected)
NRF.disconnect();
}
else if (error.message.includes("UUID 0x2a37")) {
/*
* Setup service if it wasn't setup correctly for some reason
* Setup service if it wasn't setup correctly for some reason
*/
setupHRMAdvertising();
} else {
@ -66,5 +64,5 @@
}
setupHRMAdvertising();
Bangle.on("HRM", function (hrm) { updateBLEHeartRate(hrm); });
Bangle.on("HRM", updateBLEHeartRate);
})();

View File

@ -2,7 +2,7 @@
"id": "bootgatthrm",
"name": "BLE GATT HRM Service",
"shortName": "BLE HRM Service",
"version": "0.02",
"version": "0.04",
"description": "Adds the GATT HRM Service to advertise the measured HRM over Bluetooth.\n",
"icon": "bluetooth.png",
"type": "bootloader",

View File

@ -1,2 +1,4 @@
0.01: New app!
0.02: Advertise accelerometer data and sensor location
0.03: Use the bleAdvert module
0.04: Actually use the ble_advert module

View File

@ -1,4 +1,3 @@
var _a;
{
var __assign = Object.assign;
var Layout_1 = require("Layout");
@ -441,8 +440,6 @@ var _a;
NRF.setServices(ad, {
uart: false,
});
var bangle2 = Bangle;
var cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : [];
for (var id in ad) {
var serv = ad[id];
var value = void 0;
@ -450,11 +447,7 @@ var _a;
value = serv[ch].value;
break;
}
cycle.push((_a = {}, _a[id] = value || [], _a));
require("ble_advert").set(id, value || []);
}
bangle2.bleAdvert = cycle;
NRF.setAdvertising(cycle, {
interval: 100,
});
}
}

View File

@ -1,5 +1,6 @@
{
// @ts-ignore helper
// @ts-expect-error helper
const __assign = Object.assign;
const Layout = require("Layout");
@ -666,6 +667,8 @@ const getBleAdvert = <T>(map: (s: BleServ) => T, all = false) => {
// done via advertise in setServices()
//const updateBleAdvert = () => {
// require("ble_advert").set(...)
//
// let bleAdvert: ReturnType<typeof getBleAdvert<undefined>>;
//
// if (!(bleAdvert = (Bangle as any).bleAdvert)) {
@ -764,12 +767,6 @@ enableSensors();
},
);
type BleAdvert = { [key: string]: number[] };
const bangle2 = Bangle as {
bleAdvert?: BleAdvert | BleAdvert[];
};
const cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : [];
for(const id in ad){
const serv = ad[id as BleServ];
let value;
@ -780,16 +777,7 @@ enableSensors();
break;
}
cycle.push({ [id]: value || [] });
require("ble_advert").set(id, value || []);
}
bangle2.bleAdvert = cycle;
NRF.setAdvertising(
cycle,
{
interval: 100,
}
);
}
}

View File

@ -2,7 +2,7 @@
"id": "btadv",
"name": "btadv",
"shortName": "btadv",
"version": "0.02",
"version": "0.04",
"description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth",
"icon": "icon.png",
"tags": "health,tool,sensors,bluetooth",

View File

@ -1,2 +1,6 @@
0.01: New App!
0.02: Fix double-button press if you press the next button within 30s (#3243)
0.02: Fix double-button press if you press the next button within 30s (#3243)
0.03: Cope with identical duplicate buttons (fix #3260)
Set 'n' for buttons in Bangle.btHomeData correctly (avoids adding extra buttons on end of advertising)
0.04: Fix duplicate button on edit->save
0.05: Use the bleAdvert module

View File

@ -5,20 +5,26 @@ function showMenu() {
var settings = require("Storage").readJSON("bthome.json",1)||{};
if (!(settings.buttons instanceof Array))
settings.buttons = [];
var menu = { "": {title:"BTHome", back:load} };
var menu = [];
menu[""] = {title:"BTHome", back:load };
settings.buttons.forEach((button,idx) => {
var img = require("icons").getIcon(button.icon);
menu[/*LANG*/"\0"+img+" "+button.name] = function() {
Bangle.btHome([{type:"button_event",v:button.v,n:button.n}],{event:true});
E.showMenu();
E.showMessage("Sending Event");
Bangle.buzz();
setTimeout(showMenu, 500);
};
menu.push({
title : /*LANG*/"\0"+img+" "+button.name,
onchange : function() {
Bangle.btHome([{type:"button_event",v:button.v,n:button.n}],{event:true});
E.showMenu();
E.showMessage("Sending Event");
Bangle.buzz();
setTimeout(showMenu, 500);
}
});
});
menu[/*LANG*/"Settings"] = function() {
menu.push({
title : /*LANG*/"Settings",
onchange : function() {
eval(require("Storage").read("bthome.settings.js"))(()=>showMenu());
};
}});
E.showMenu(menu);
}

View File

@ -1,5 +1,3 @@
// Ensure we have the bleAdvert global (to play well with other stuff)
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
Bangle.btHomeData = [];
{
require("BTHome").packetId = 0|(Math.random()*256); // random packet id so new packets show up
@ -13,7 +11,7 @@ Bangle.btHomeData = [];
if (settings.buttons instanceof Array) {
let n = settings.buttons.reduce((n,b)=>b.n>n?b.n:n,-1);
for (var i=0;i<=n;i++)
Bangle.btHomeData.push({type:"button_event",v:"none",n:n});
Bangle.btHomeData.push({type:"button_event",v:"none",n:i});
}
}
@ -39,20 +37,6 @@ Bangle.btHome = function(extras, options) {
if (bat) bat.v = E.getBattery();
var advert = require("BTHome").getAdvertisement(Bangle.btHomeData)[0xFCD2];
// Add to the list of available advertising
if(Array.isArray(Bangle.bleAdvert)){
var found = false;
for(var ad in Bangle.bleAdvert){
if(ad[0xFCD2]){
ad[0xFCD2] = advert;
found = true;
break;
}
}
if(!found)
Bangle.bleAdvert.push({ 0xFCD2: advert });
} else {
Bangle.bleAdvert[0xFCD2] = advert;
}
var advOptions = {};
var updateTimeout = 10*60*1000; // update every 10 minutes
if (options.event) { // if it's an event...
@ -60,7 +44,7 @@ Bangle.btHome = function(extras, options) {
advOptions.whenConnected = true;
updateTimeout = 30000; // slow down in 30 seconds
}
NRF.setAdvertising(Bangle.bleAdvert, advOptions);
require("ble_advert").set(0xFCD2, advert, advOptions);
if (Bangle.btHomeTimeout) clearTimeout(Bangle.btHomeTimeout);
Bangle.btHomeTimeout = setTimeout(function() {
delete Bangle.btHomeTimeout;

View File

@ -1,7 +1,7 @@
{ "id": "bthome",
"name": "BTHome",
"shortName":"BTHome",
"version":"0.02",
"version":"0.05",
"description": "Allow your Bangle to advertise with BTHome and send events to Home Assistant via Bluetooth",
"icon": "icon.png",
"type": "app",

View File

@ -1,7 +1,11 @@
(function(back) {
var settings = require("Storage").readJSON("bthome.json",1)||{};
if (!(settings.buttons instanceof Array))
settings.buttons = [];
var settings;
function loadSettings() {
settings = require("Storage").readJSON("bthome.json",1)||{};
if (!(settings.buttons instanceof Array))
settings.buttons = [];
}
function saveSettings() {
require("Storage").writeJSON("bthome.json",settings)
@ -15,7 +19,10 @@
}
var actions = ["press","double_press","triple_press","long_press","long_double_press","long_triple_press"];
var menu = {
"":{title:isNew ? /*LANG*/"New Button" : /*LANG*/"Edit Button", back:showMenu},
"":{title:isNew ? /*LANG*/"New Button" : /*LANG*/"Edit Button", back: () => {
loadSettings(); // revert changes
showMenu();
}},
/*LANG*/"Icon" : {
value : "\0"+require("icons").getIcon(button.icon),
onchange : () => {
@ -49,7 +56,7 @@
onchange : v => button.n=v
},
/*LANG*/"Save" : () => {
settings.buttons.push(button);
if (isNew) settings.buttons.push(button);
saveSettings();
showMenu();
}
@ -67,25 +74,34 @@
}
function showMenu() {
var menu = { "": {title:"BTHome", back:back},
/*LANG*/"Show Battery" : {
value : !!settings.showBattery,
onchange : v=>{
settings.showBattery = v;
saveSettings();
}
var menu = [];
menu[""] = {title:"BTHome", back:back};
menu.push({
title : /*LANG*/"Show Battery",
value : !!settings.showBattery,
onchange : v=>{
settings.showBattery = v;
saveSettings();
}
};
});
settings.buttons.forEach((button,idx) => {
var img = require("icons").getIcon(button.icon);
menu[/*LANG*/"Button"+(img ? " \0"+img : (idx+1))] = function() {
showButtonMenu(button, false);
};
menu.push({
title : /*LANG*/"Button"+(img ? " \0"+img : (idx+1)),
onchange : function() {
showButtonMenu(button, false);
}
});
});
menu.push({
title : /*LANG*/"Add Button",
onchange : function() {
showButtonMenu(undefined, true);
}
});
menu[/*LANG*/"Add Button"] = function() {
showButtonMenu(undefined, true);
};
E.showMenu(menu);
}
loadSettings();
showMenu();
})

View File

@ -1,2 +1,3 @@
0.01: New App!
0.02: Handle the case where other apps have set bleAdvert to an array
0.03: Use the ble_advert module

View File

@ -38,21 +38,7 @@ function onTemperature(p) {
pressure100&255,(pressure100>>8)&255,pressure100>>16
];
if(Array.isArray(Bangle.bleAdvert)){
var found = false;
for(var ad in Bangle.bleAdvert){
if(ad[0xFCD2]){
ad[0xFCD2] = advert;
found = true;
break;
}
}
if(!found)
Bangle.bleAdvert.push({ 0xFCD2: advert });
}else{
Bangle.bleAdvert[0xFCD2] = advert;
}
NRF.setAdvertising(Bangle.bleAdvert);
require("ble_advert").set(0xFCD2, advert);
}
// Gets the temperature in the most accurate way with pressure sensor
@ -60,7 +46,6 @@ function drawTemperature() {
Bangle.getPressure().then(p =>{if (p) onTemperature(p);});
}
if (!Bangle.bleAdvert) Bangle.bleAdvert = {};
setInterval(function() {
drawTemperature();
}, 10000); // update every 10s

View File

@ -1,7 +1,7 @@
{ "id": "bthometemp",
"name": "BTHome Temperature and Pressure",
"shortName":"BTHome T",
"version":"0.02",
"version":"0.03",
"description": "Displays temperature and pressure, and advertises them over bluetooth for Home Assistant using BTHome.io standard",
"icon": "app.png",
"tags": "bthome,bluetooth,temperature",

View File

@ -42,3 +42,8 @@
Add debug option for disabling active scanning
0.17: New GUI based on layout library
0.18: Minor code improvements
0.19: Move caching of characteristics into settings app
Changed default of active scanning to false
Fix setHRMPower method not returning new state
Only buzz for disconnect after switching on if there already was an actual connection
Fix recorder not switching BTHRM on and off

View File

@ -21,6 +21,10 @@ Once installed you will have to go into this app's settings while your heart rat
**To disable this and return to normal HRM, uninstall the app or change the settings**
The characteristics of your selected sensor are cached in the settings. That means if your sensor changes, e.g. by firmware updates or similar, you will need to re-scan in the settings to update the cache of characteristics. This is done to take some complexity (and time) out of the boot process.
Scanning in the settings will do 10 retries and then give up on adding the sensor. Usually that works fine, if it does not for you just try multiple times. Currently saved sensor information is only replaced on a successful pairing. There are additional options in the Debug entry of the menu that can help with specific sensor oddities. Bonding and active scanning can help with connecting, but can also prevent some sensors from working. The "Grace Periods" just add some additional time at certain steps in the connection process which can help with stability or reconnect speed of some finicky sensors. Defaults should be fine for most.
### Modes
* Off - Internal HRM is used, no attempt on connecting to BT HRM.
@ -57,3 +61,7 @@ This replaces `Bangle.setHRMPower` with its own implementation.
## Creator
Gordon Williams
## Contributer
[halemmerich](https://github.com/halemmerich)

View File

@ -57,7 +57,7 @@ var layout = new Layout( {
{ type:undefined, height:8 } //dummy to protect debug output
]
}, {
lazy:true
lazy:false
});
var int,agg,bt;
@ -106,8 +106,7 @@ function draw(){
layout.btContact.label = "--";
layout.btEnergy.label = "--";
}
layout.update();
layout.clear();
layout.render();
let first = true;
for (let c of layout.l.c){
@ -122,26 +121,29 @@ function draw(){
// This can get called for the boot code to show what's happening
function showStatusInfo(txt) {
global.showStatusInfo = function(txt) {
var R = Bangle.appRect;
g.reset().clearRect(R.x,R.y2-8,R.x2,R.y2).setFont("6x8");
txt = g.wrapString(txt, R.w)[0];
g.setFontAlign(0,1).drawString(txt, (R.x+R.x2)/2, R.y2);
}
};
function onBtHrm(e) {
bt = e;
bt.time = Date.now();
draw();
}
function onInt(e) {
int = e;
int.time = Date.now();
draw();
}
function onAgg(e) {
agg = e;
agg.time = Date.now();
draw();
}
var settings = require('Storage').readJSON("bthrm.json", true) || {};
@ -162,7 +164,6 @@ Bangle.drawWidgets();
if (Bangle.setBTHRMPower){
g.reset().setFont("6x8",2).setFontAlign(0,0);
g.drawString("Please wait...",g.getWidth()/2,g.getHeight()/2);
setInterval(draw, 1000);
} else {
g.reset().setFont("6x8",2).setFontAlign(0,0);
g.drawString("BTHRM disabled",g.getWidth()/2,g.getHeight()/2);

View File

@ -15,8 +15,7 @@
"custom_fallbackTimeout": 10,
"gracePeriodNotification": 0,
"gracePeriodConnect": 0,
"gracePeriodService": 0,
"gracePeriodRequest": 0,
"bonding": false,
"active": true
"active": false
}

View File

@ -1,14 +1,14 @@
exports.enable = () => {
var settings = Object.assign(
let settings = Object.assign(
require('Storage').readJSON("bthrm.default.json", true) || {},
require('Storage').readJSON("bthrm.json", true) || {}
);
var log = function(text, param){
let log = function(text, param){
if (global.showStatusInfo)
global.showStatusInfo(text);
if (settings.debuglog){
var logline = new Date().toISOString() + " - " + text;
let logline = new Date().toISOString() + " - " + text;
if (param) logline += ": " + JSON.stringify(param);
print(logline);
}
@ -16,60 +16,33 @@ exports.enable = () => {
log("Settings: ", settings);
if (settings.enabled){
//this is for compatibility with 0.18 and older
let oldCache = require('Storage').readJSON("bthrm.cache.json", true);
if(oldCache){
settings.cache = oldCache;
require('Storage').writeJSON("bthrm.json", settings);
require('Storage').erase("bthrm.cache.json");
}
var clearCache = function() {
return require('Storage').erase("bthrm.cache.json");
};
if (settings.enabled && settings.cache){
var getCache = function() {
var cache = require('Storage').readJSON("bthrm.cache.json", true) || {};
if (settings.btid && settings.btid === cache.id) return cache;
clearCache();
return {};
};
log("Start");
var addNotificationHandler = function(characteristic) {
let addNotificationHandler = function(characteristic) {
log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/);
characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
};
var writeCache = function(cache) {
var oldCache = getCache();
if (oldCache !== cache) {
log("Writing cache");
require('Storage').writeJSON("bthrm.cache.json", cache);
} else {
log("No changes, don't write cache");
}
};
var characteristicsToCache = function(characteristics) {
log("Cache characteristics");
var cache = getCache();
if (!cache.characteristics) cache.characteristics = {};
for (var c of characteristics){
//"handle_value":16,"handle_decl":15
log("Saving handle " + c.handle_value + " for characteristic: ", c);
cache.characteristics[c.uuid] = {
"handle": c.handle_value,
"uuid": c.uuid,
"notify": c.properties.notify,
"read": c.properties.read
};
}
writeCache(cache);
};
var characteristicsFromCache = function(device) {
var service = { device : device }; // fake a BluetoothRemoteGATTService
let characteristicsFromCache = function(device) {
let service = { device : device }; // fake a BluetoothRemoteGATTService
log("Read cached characteristics");
var cache = getCache();
let cache = settings.cache;
if (!cache.characteristics) return [];
var restored = [];
for (var c in cache.characteristics){
var cached = cache.characteristics[c];
var r = new BluetoothRemoteGATTCharacteristic();
let restored = [];
for (let c in cache.characteristics){
let cached = cache.characteristics[c];
let r = new BluetoothRemoteGATTCharacteristic();
log("Restoring characteristic ", cached);
r.handle_value = cached.handle;
r.uuid = cached.uuid;
@ -84,26 +57,14 @@ exports.enable = () => {
return restored;
};
log("Start");
var lastReceivedData={
};
var supportedServices = [
"0x180d", // Heart Rate
"0x180f", // Battery
];
var bpmTimeout;
var supportedCharacteristics = {
let supportedCharacteristics = {
"0x2a37": {
//Heart rate measurement
active: false,
handler: function (dv){
var flags = dv.getUint8(0);
let flags = dv.getUint8(0);
var bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
let bpm = (flags & 1) ? (dv.getUint16(1) / 100 /* ? */ ) : dv.getUint8(1); // 8 or 16 bit
supportedCharacteristics["0x2a37"].active = bpm > 0;
log("BTHRM BPM " + supportedCharacteristics["0x2a37"].active);
switchFallback();
@ -114,42 +75,42 @@ exports.enable = () => {
startFallback();
}, 3000);
var sensorContact;
let sensorContact;
if (flags & 2){
sensorContact = !!(flags & 4);
}
var idx = 2 + (flags&1);
let idx = 2 + (flags&1);
var energyExpended;
let energyExpended;
if (flags & 8){
energyExpended = dv.getUint16(idx,1);
idx += 2;
}
var interval;
let interval;
if (flags & 16) {
interval = [];
var maxIntervalBytes = (dv.byteLength - idx);
let maxIntervalBytes = (dv.byteLength - idx);
log("Found " + (maxIntervalBytes / 2) + " rr data fields");
for(var i = 0 ; i < maxIntervalBytes / 2; i++){
for(let i = 0 ; i < maxIntervalBytes / 2; i++){
interval[i] = dv.getUint16(idx,1); // in milliseconds
idx += 2;
}
}
var location;
let location;
if (lastReceivedData && lastReceivedData["0x180d"] && lastReceivedData["0x180d"]["0x2a38"]){
location = lastReceivedData["0x180d"]["0x2a38"];
}
var battery;
let battery;
if (lastReceivedData && lastReceivedData["0x180f"] && lastReceivedData["0x180f"]["0x2a19"]){
battery = lastReceivedData["0x180f"]["0x2a19"];
}
if (settings.replace && bpm > 0){
var repEvent = {
let repEvent = {
bpm: bpm,
confidence: (sensorContact || sensorContact === undefined)? 100 : 0,
src: "bthrm"
@ -159,7 +120,7 @@ exports.enable = () => {
Bangle.emit("HRM_R", repEvent);
}
var newEvent = {
let newEvent = {
bpm: bpm
};
@ -177,6 +138,7 @@ exports.enable = () => {
//Body sensor location
handler: function(dv){
if (!lastReceivedData["0x180d"]) lastReceivedData["0x180d"] = {};
log("Got location", dv);
lastReceivedData["0x180d"]["0x2a38"] = parseInt(dv.buffer, 10);
}
},
@ -184,26 +146,27 @@ exports.enable = () => {
//Battery
handler: function (dv){
if (!lastReceivedData["0x180f"]) lastReceivedData["0x180f"] = {};
log("Got battery", dv);
lastReceivedData["0x180f"]["0x2a19"] = dv.getUint8(0);
}
}
};
var device;
var gatt;
var characteristics = [];
var blockInit = false;
var currentRetryTimeout;
var initialRetryTime = 40;
var maxRetryTime = 60000;
var retryTime = initialRetryTime;
var connectSettings = {
minInterval: 7.5,
maxInterval: 1500
let lastReceivedData={
};
var waitingPromise = function(timeout) {
let bpmTimeout;
let device;
let gatt;
let characteristics = [];
let blockInit = false;
let currentRetryTimeout;
let initialRetryTime = 40;
let maxRetryTime = 60000;
let retryTime = initialRetryTime;
let waitingPromise = function(timeout) {
return new Promise(function(resolve){
log("Start waiting for " + timeout);
setTimeout(()=>{
@ -240,7 +203,7 @@ exports.enable = () => {
};
}
var clearRetryTimeout = function(resetTime) {
let clearRetryTimeout = function(resetTime) {
if (currentRetryTimeout){
log("Clearing timeout " + currentRetryTimeout);
clearTimeout(currentRetryTimeout);
@ -252,12 +215,12 @@ exports.enable = () => {
}
};
var retry = function() {
let retry = function() {
log("Retry");
if (!currentRetryTimeout && !powerdownRequested){
var clampedTime = retryTime < 100 ? 100 : retryTime;
let clampedTime = retryTime < 100 ? 100 : retryTime;
log("Set timeout for retry as " + clampedTime);
clearRetryTimeout();
@ -276,20 +239,21 @@ exports.enable = () => {
}
};
var buzzing = false;
var onDisconnect = function(reason) {
let initialDisconnects = true;
let buzzing = false;
let onDisconnect = function(reason) {
log("Disconnect: " + reason);
log("GATT", gatt);
log("Characteristics", characteristics);
var retryTimeResetNeeded = true;
let retryTimeResetNeeded = true;
retryTimeResetNeeded &= reason != "Connection Timeout";
retryTimeResetNeeded &= reason != "No device found matching filters";
clearRetryTimeout(retryTimeResetNeeded);
supportedCharacteristics["0x2a37"].active = false;
if (!powerdownRequested) startFallback();
blockInit = false;
if (settings.warnDisconnect && !buzzing){
if (settings.warnDisconnect && !buzzing && !initialDisconnects){
buzzing = true;
Bangle.buzz(500,0.3).then(()=>waitingPromise(4500)).then(()=>{buzzing = false;});
}
@ -298,9 +262,9 @@ exports.enable = () => {
}
};
var createCharacteristicPromise = function(newCharacteristic) {
let createCharacteristicPromise = function(newCharacteristic) {
log("Create characteristic promise", newCharacteristic);
var result = Promise.resolve();
let result = Promise.resolve();
// For values that can be read, go ahead and read them, even if we might be notified in the future
// Allows for getting initial state of infrequently updating characteristics, like battery
if (newCharacteristic.readValue){
@ -316,58 +280,29 @@ exports.enable = () => {
if (newCharacteristic.properties.notify){
result = result.then(()=>{
log("Starting notifications", newCharacteristic);
var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
startPromise = startPromise.then(()=>{
log("Wait after connect");
return waitingPromise(settings.gracePeriodNotification);
});
let startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
if (settings.gracePeriodNotification){
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
startPromise = startPromise.then(()=>{
log("Wait after connect");
return waitingPromise(settings.gracePeriodNotification);
});
}
return startPromise;
});
}
return result.then(()=>log("Handled characteristic", newCharacteristic));
};
var attachCharacteristicPromise = function(promise, characteristic) {
let attachCharacteristicPromise = function(promise, characteristic) {
return promise.then(()=>{
log("Handling characteristic:", characteristic);
return createCharacteristicPromise(characteristic);
});
};
var createCharacteristicsPromise = function(newCharacteristics) {
log("Create characteristics promis ", newCharacteristics);
var result = Promise.resolve();
for (var c of newCharacteristics){
if (!supportedCharacteristics[c.uuid]) continue;
log("Supporting characteristic", c);
characteristics.push(c);
if (c.properties.notify){
addNotificationHandler(c);
}
result = attachCharacteristicPromise(result, c);
}
return result.then(()=>log("Handled characteristics"));
};
var createServicePromise = function(service) {
log("Create service promise", service);
var result = Promise.resolve();
result = result.then(()=>{
log("Handling service" + service.uuid);
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
});
return result.then(()=>log("Handled service" + service.uuid));
};
var attachServicePromise = function(promise, service) {
return promise.then(()=>createServicePromise(service));
};
var initBt = function () {
let initBt = function () {
log("initBt with blockInit: " + blockInit);
if (blockInit && !powerdownRequested){
retry();
@ -376,8 +311,8 @@ exports.enable = () => {
blockInit = true;
var promise;
var filters;
let promise;
let filters;
if (!device){
if (settings.btid){
@ -394,9 +329,13 @@ exports.enable = () => {
onDisconnect(e);
return;
}
if (settings.gracePeriodRequest){
log("Add " + settings.gracePeriodRequest + "ms grace period after request");
promise = promise.then((d)=>{
log("Wait after request");
return waitingPromise(settings.gracePeriodRequest).then(()=>Promise.resolve(d));
});
}
promise = promise.then((d)=>{
@ -404,100 +343,42 @@ exports.enable = () => {
d.on('gattserverdisconnected', onDisconnect);
device = d;
});
promise = promise.then(()=>{
log("Wait after request");
return waitingPromise(settings.gracePeriodRequest);
});
} else {
promise = Promise.resolve();
log("Reuse device", device);
}
promise = promise.then(()=>{
if (gatt){
log("Reuse GATT", gatt);
} else {
log("GATT is new", gatt);
characteristics = [];
var cachedId = getCache().id;
if (device.id !== cachedId){
log("Device ID changed from " + cachedId + " to " + device.id + ", clearing cache");
clearCache();
}
var newCache = getCache();
newCache.id = device.id;
writeCache(newCache);
gatt = device.gatt;
}
gatt = device.gatt;
return Promise.resolve(gatt);
});
promise = promise.then((gatt)=>{
if (!gatt.connected){
log("Connecting...");
var connectPromise = gatt.connect(connectSettings).then(function() {
let connectPromise = gatt.connect().then(function() {
log("Connected.");
});
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
connectPromise = connectPromise.then(()=>{
log("Wait after connect");
return waitingPromise(settings.gracePeriodConnect);
});
if (settings.gracePeriodConnect){
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
connectPromise = connectPromise.then(()=>{
log("Wait after connect");
return waitingPromise(settings.gracePeriodConnect);
});
}
return connectPromise;
} else {
return Promise.resolve();
}
});
if (settings.bonding){
promise = promise.then(() => {
log(JSON.stringify(gatt.getSecurityStatus()));
if (gatt.getSecurityStatus()['bonded']) {
log("Already bonded");
return Promise.resolve();
} else {
log("Start bonding");
return gatt.startBonding()
.then(() => log("Security status" + gatt.getSecurityStatus()));
}
});
}
promise = promise.then(()=>{
if (!characteristics || characteristics.length === 0){
if (!characteristics || characteristics.length == 0){
characteristics = characteristicsFromCache(device);
}
});
promise = promise.then(()=>{
var characteristicsPromise = Promise.resolve();
if (characteristics.length === 0){
characteristicsPromise = characteristicsPromise.then(()=>{
log("Getting services");
return gatt.getPrimaryServices();
});
characteristicsPromise = characteristicsPromise.then((services)=>{
log("Got services", services);
var result = Promise.resolve();
for (var service of services){
if (!(supportedServices.includes(service.uuid))) continue;
log("Supporting service", service.uuid);
result = attachServicePromise(result, service);
}
log("Add " + settings.gracePeriodService + "ms grace period after services");
result = result.then(()=>{
log("Wait after services");
return waitingPromise(settings.gracePeriodService);
});
return result;
});
} else {
for (var characteristic of characteristics){
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
}
let characteristicsPromise = Promise.resolve();
for (let characteristic of characteristics){
characteristicsPromise = attachCharacteristicPromise(characteristicsPromise, characteristic, true);
}
return characteristicsPromise;
@ -505,7 +386,7 @@ exports.enable = () => {
return promise.then(()=>{
log("Connection established, waiting for notifications");
characteristicsToCache(characteristics);
initialDisconnects = false;
clearRetryTimeout(true);
}).catch((e) => {
characteristics = [];
@ -514,7 +395,7 @@ exports.enable = () => {
});
};
var powerdownRequested = false;
let powerdownRequested = false;
Bangle.setBTHRMPower = function(isOn, app) {
// Do app power handling
@ -526,6 +407,7 @@ exports.enable = () => {
isOn = Bangle._PWR.BTHRM.length;
// so now we know if we're really on
if (isOn) {
initialDisconnects = true;
powerdownRequested = false;
switchFallback();
if (!Bangle.isBTHRMConnected()) initBt();
@ -598,17 +480,18 @@ exports.enable = () => {
Bangle.setBTHRMPower(0);
if (!isOn) stopFallback();
}
return Bangle.isBTHRMOn() || Bangle.isHRMOn();
}
if ((settings.enabled && !settings.replace) || !settings.enabled){
Bangle.origSetHRMPower(isOn, app);
return Bangle.origSetHRMPower(isOn, app);
}
};
}
var fallbackActive = false;
var inSwitch = false;
let fallbackActive = false;
let inSwitch = false;
var stopFallback = function(){
let stopFallback = function(){
if (fallbackActive){
Bangle.origSetHRMPower(0, "bthrm_fallback");
fallbackActive = false;
@ -616,7 +499,7 @@ exports.enable = () => {
}
};
var startFallback = function(){
let startFallback = function(){
if (!fallbackActive && settings.allowFallback) {
fallbackActive = true;
Bangle.origSetHRMPower(1, "bthrm_fallback");
@ -624,7 +507,7 @@ exports.enable = () => {
}
};
var switchFallback = function() {
let switchFallback = function() {
log("Check falling back to HRM");
if (!inSwitch){
inSwitch = true;
@ -640,8 +523,8 @@ exports.enable = () => {
if (settings.replace){
log("Replace HRM event");
if (Bangle._PWR && Bangle._PWR.HRM){
for (var i = 0; i < Bangle._PWR.HRM.length; i++){
var app = Bangle._PWR.HRM[i];
for (let i = 0; i < Bangle._PWR.HRM.length; i++){
let app = Bangle._PWR.HRM[i];
log("Moving app " + app);
Bangle.origSetHRMPower(0, app);
Bangle.setBTHRMPower(1, app);

View File

@ -2,7 +2,7 @@
"id": "bthrm",
"name": "Bluetooth Heart Rate Monitor",
"shortName": "BT HRM",
"version": "0.18",
"version": "0.19",
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
"icon": "app.png",
"screenshots": [{"url":"screen.png"}],

View File

@ -26,11 +26,11 @@
},
start : () => {
Bangle.on('BTHRM', onHRM);
if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(1,"recorder");
if (Bangle.setBTHRMPower) Bangle.setBTHRMPower(1,"recorder");
},
stop : () => {
Bangle.removeListener('BTHRM', onHRM);
if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(0,"recorder");
if (Bangle.setBTHRMPower) Bangle.setBTHRMPower(0,"recorder");
},
draw : (x,y) => g.setColor((Bangle.isBTHRMActive && Bangle.isBTHRMActive())?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
};

View File

@ -1,6 +1,6 @@
(function(back) {
function writeSettings(key, value) {
var s = require('Storage').readJSON(FILE, true) || {};
let s = require('Storage').readJSON(FILE, true) || {};
s[key] = value;
require('Storage').writeJSON(FILE, s);
readSettings();
@ -13,10 +13,14 @@
);
}
var FILE="bthrm.json";
var settings;
let FILE="bthrm.json";
let settings;
readSettings();
let log = ()=>{};
if (settings.debuglog)
log = print;
function applyCustomSettings(){
writeSettings("enabled",true);
writeSettings("replace",settings.custom_replace);
@ -26,7 +30,7 @@
}
function buildMainMenu(){
var mainmenu = {
let mainmenu = {
'': { 'title': 'Bluetooth HRM' },
'< Back': back,
'Mode': {
@ -63,12 +67,13 @@
};
if (settings.btname || settings.btid){
var name = "Clear " + (settings.btname || settings.btid);
let name = "Clear " + (settings.btname || settings.btid);
mainmenu[name] = function() {
E.showPrompt("Clear current device?").then((r)=>{
if (r) {
writeSettings("btname",undefined);
writeSettings("btid",undefined);
writeSettings("cache", undefined);
}
E.showMenu(buildMainMenu());
});
@ -81,7 +86,7 @@
return mainmenu;
}
var submenu_debug = {
let submenu_debug = {
'' : { title: "Debug"},
'< Back': function() { E.showMenu(buildMainMenu()); },
'Alert on disconnect': {
@ -111,11 +116,135 @@
'Grace periods': function() { E.showMenu(submenu_grace); }
};
let supportedServices = [
"0x180d", // Heart Rate
"0x180f", // Battery
];
let supportedCharacteristics = [
"0x2a37", // Heart Rate
"0x2a38", // Body sensor location
"0x2a19", // Battery
];
var characteristicsToCache = function(characteristics) {
log("Cache characteristics");
let cache = {};
if (!cache.characteristics) cache.characteristics = {};
for (var c of characteristics){
//"handle_value":16,"handle_decl":15
log("Saving handle " + c.handle_value + " for characteristic: ", c.uuid);
cache.characteristics[c.uuid] = {
"handle": c.handle_value,
"uuid": c.uuid,
"notify": c.properties.notify,
"read": c.properties.read
};
}
writeSettings("cache", cache);
};
let createCharacteristicPromise = function(newCharacteristic) {
log("Create characteristic promise", newCharacteristic.uuid);
return Promise.resolve().then(()=>log("Handled characteristic", newCharacteristic.uuid));
};
let attachCharacteristicPromise = function(promise, characteristic) {
return promise.then(()=>{
log("Handling characteristic:", characteristic.uuid);
return createCharacteristicPromise(characteristic);
});
};
let characteristics;
let createCharacteristicsPromise = function(newCharacteristics) {
log("Create characteristics promise ", newCharacteristics.length);
let result = Promise.resolve();
for (let c of newCharacteristics){
if (!supportedCharacteristics.includes(c.uuid)) continue;
log("Supporting characteristic", c.uuid);
characteristics.push(c);
result = attachCharacteristicPromise(result, c);
}
return result.then(()=>log("Handled characteristics"));
};
let createServicePromise = function(service) {
log("Create service promise", service.uuid);
let result = Promise.resolve();
result = result.then(()=>{
log("Handling service", service.uuid);
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
});
return result.then(()=>log("Handled service", service.uuid));
};
let attachServicePromise = function(promise, service) {
return promise.then(()=>createServicePromise(service));
};
function cacheDevice(deviceId){
let promise;
let filters;
let gatt;
characteristics = [];
filters = [{ id: deviceId }];
log("Requesting device with filters", filters);
promise = NRF.requestDevice({ filters: filters, active: settings.active });
promise = promise.then((d)=>{
log("Got device", d);
gatt = d.gatt;
log("Connecting...");
return gatt.connect().then(function() {
log("Connected.");
});
});
if (settings.bonding){
promise = promise.then(() => {
log(JSON.stringify(gatt.getSecurityStatus()));
if (gatt.getSecurityStatus().bonded) {
log("Already bonded");
return Promise.resolve();
} else {
log("Start bonding");
return gatt.startBonding()
.then(() => log("Security status after bonding" + gatt.getSecurityStatus()));
}
});
}
promise = promise.then(()=>{
log("Getting services");
return gatt.getPrimaryServices();
});
promise = promise.then((services)=>{
log("Got services", services.length);
let result = Promise.resolve();
for (let service of services){
if (!(supportedServices.includes(service.uuid))) continue;
log("Supporting service", service.uuid);
result = attachServicePromise(result, service);
}
return result;
});
return promise.then(()=>{
log("Connection established, saving cache");
characteristicsToCache(characteristics);
});
}
function createMenuFromScan(){
E.showMenu();
E.showMessage("Scanning for 4 seconds");
var submenu_scan = {
let submenu_scan = {
'< Back': function() { E.showMenu(buildMainMenu()); }
};
NRF.findDevices(function(devices) {
@ -126,18 +255,41 @@
return;
} else {
devices.forEach((d) => {
print("Found device", d);
var shown = (d.name || d.id.substr(0, 17));
log("Found device", d);
let shown = (d.name || d.id.substr(0, 17));
submenu_scan[shown] = function () {
E.showPrompt("Set " + shown + "?").then((r) => {
E.showPrompt("Connect to\n" + shown + "?", {title: "Pairing"}).then((r) => {
if (r) {
writeSettings("btid", d.id);
// Store the name for displaying later. Will connect by ID
if (d.name) {
writeSettings("btname", d.name);
}
E.showMessage("Connecting...", {img:require("Storage").read("bthrm.img")});
let count = 0;
const successHandler = ()=>{
E.showPrompt("Success!", {
img:require("Storage").read("bthrm.img"),
buttons: { "OK":true }
}).then(()=>{
writeSettings("btid", d.id);
// Store the name for displaying later. Will connect by ID
if (d.name) {
writeSettings("btname", d.name);
}
E.showMenu(buildMainMenu());
});
};
const errorHandler = (e)=>{
count++;
log("ERROR", e);
if (count <= 10){
E.showMessage("Error during caching\nRetry " + count + "/10", e);
return cacheDevice(d.id).then(successHandler).catch(errorHandler);
} else {
E.showAlert("Error during caching", e).then(()=>{
E.showMenu(buildMainMenu());
});
}
};
return cacheDevice(d.id).then(successHandler).catch(errorHandler);
}
E.showMenu(buildMainMenu());
});
};
});
@ -146,7 +298,7 @@
}, { timeout: 4000, active: true, filters: [{services: [ "180d" ]}]});
}
var submenu_custom = {
let submenu_custom = {
'' : { title: "Custom mode"},
'< Back': function() { E.showMenu(buildMainMenu()); },
'Replace HRM': {
@ -183,7 +335,7 @@
},
};
var submenu_grace = {
let submenu_grace = {
'' : { title: "Grace periods"},
'< Back': function() { E.showMenu(submenu_debug); },
'Request': {
@ -215,16 +367,6 @@
onchange: v => {
writeSettings("gracePeriodNotification",v);
}
},
'Service': {
value: settings.gracePeriodService,
min: 0,
max: 3000,
step: 100,
format: v=>v+"ms",
onchange: v => {
writeSettings("gracePeriodService",v);
}
}
};

View File

@ -1,35 +0,0 @@
{
"env": {
"browser": true,
"commonjs": true,
"es6": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
"indent": [
"error",
2,
{ "SwitchCase": 1 }
],
"linebreak-style": [
"error",
"windows"
],
"quotes": [
"error",
"double"
]
/*,
"semi": [
"error",
"always"
]*/
}
}

View File

@ -2,3 +2,4 @@
0.02: Hiding widgets while showing the code
0.03: Added option to use max brightness when showing code
0.04: Minor code improvements
0.05: Add EAN & UPC codes

View File

@ -82,16 +82,17 @@ function printSquareCode(binary, size) {
}
}
function printLinearCode(binary) {
var padding = 5;
var yFrom = 15;
var yTo = 28;
var width = g.getWidth()/binary.length;
var width = (g.getWidth()-(2*padding))/binary.length;
for(var b = 0; b < binary.length; b++){
var x = b * width;
if(binary[b] === "1"){
g.setColor(BLACK).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
g.setColor(BLACK).fillRect({x:x+padding, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
}
else if(binary[b]){
g.setColor(WHITE).fillRect({x:x, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
g.setColor(WHITE).fillRect({x:x+padding, y:yFrom, w:width, h:g.getHeight() - (yTo+yFrom)});
}
}
}
@ -132,6 +133,42 @@ function showCode(card) {
printLinearCode(code.encode().data);
break;
}
case "EAN_8": {
g.setFont("Vector:20");
g.setFontAlign(0,1).setColor(BLACK);
g.drawString(card.value, g.getWidth()/2, g.getHeight());
const EAN8 = require("cards.EAN8.js");
let code = new EAN8(card.value, {});
printLinearCode(code.encode().data);
break;
}
case "EAN_13": {
g.setFont("Vector:20");
g.setFontAlign(0,1).setColor(BLACK);
g.drawString(card.value, g.getWidth()/2, g.getHeight());
const EAN13 = require("cards.EAN13.js");
let code = new EAN13(card.value, {});
printLinearCode(code.encode().data);
break;
}
case "UPC_A": {
g.setFont("Vector:20");
g.setFontAlign(0,1).setColor(BLACK);
g.drawString(card.value, g.getWidth()/2, g.getHeight());
const UPC = require("cards.UPC.js");
let code = new UPC.UPC(card.value, {});
printLinearCode(code.encode().data);
break;
}
case "UPC_E": {
g.setFont("Vector:20");
g.setFontAlign(0,1).setColor(BLACK);
g.drawString(card.value, g.getWidth()/2, g.getHeight());
const UPCE = require("cards.UPCE.js");
let code = new UPCE(card.value, {});
printLinearCode(code.encode().data);
break;
}
default:
g.clear(true);
g.setFont("Vector:30");

View File

@ -1,12 +1,13 @@
{
"id": "cards",
"name": "Cards",
"version": "0.04",
"version": "0.05",
"description": "Display loyalty cards",
"icon": "app.png",
"screenshots": [{"url":"screenshot_cards_overview.png"}, {"url":"screenshot_cards_card1.png"}, {"url":"screenshot_cards_card2.png"}, {"url":"screenshot_cards_barcode.png"}, {"url":"screenshot_cards_qrcode.png"}],
"tags": "cards",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": true,
"readme": "README.md",
"storage": [
{"name":"cards.app.js","url":"app.js"},
@ -15,6 +16,12 @@
{"name":"cards.qrcode.js","url":"qrcode.js"},
{"name":"cards.codabar.js","url":"codabar.js"},
{"name":"cards.code39.js","url":"code39.js"},
{"name":"cards.EAN.js","url":"EAN.js"},
{"name":"cards.EAN8.js","url":"EAN8.js"},
{"name":"cards.EAN13.js","url":"EAN13.js"},
{"name":"cards.UPC.js","url":"UPC.js"},
{"name":"cards.UPCE.js","url":"UPCE.js"},
{"name":"cards.encode.js","url":"encode.js"},
{"name":"cards.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"cards.settings.json"}]

View File

@ -3,3 +3,5 @@
0.03: Added threshold
0.04: Added notification
0.05: Fixed boot
0.06: Allow tap to silence notification/buzzing
0.07: Fix notification-tap silencing and notification length

View File

@ -6,10 +6,12 @@ The first stage of charging Li-ion ends at ~80% capacity when the charge voltage
This app has no UI and no configuration. To disable the app, you have to uninstall it.
Tap the charged notification to prevent buzzing for this charging session.
New in v0.03: before the very first buzz, the average value after the peak is written to chargent.json and used as threshold for future charges. This reduces the time spent in the second charge stage.
Side notes
- Full capacity is reached after charge current drops to an insignificant level. This is quite some time after charge voltage reached its peak / `E.getBattery()` returns 100.
- This app starts buzzing some time after `E.getBattery()` returns 100 (~15min on my watch), and at least 5min after the peak to account for noise.
\* according to https://batteryuniversity.com/article/bu-409-charging-lithium-ion assuming similar characteristics and readouts from pin `D30` approximate charge voltage
\* according to https://batteryuniversity.com/article/bu-409-charging-lithium-ion assuming similar characteristics and readouts from pin `D30` approximate charge voltage

View File

@ -24,7 +24,8 @@
lim = sum / cnt;
require('Storage').writeJSON('chargent.json', {limit: lim});
}
require('notify').show({id: 'chargent', title: 'Fully charged'});
const onHide = () => { if(id) id = clearInterval(id) };
require('notify').show({id: 'chargent', title: 'Charged', onHide });
// TODO ? customizable
Bangle.buzz(500);
setTimeout(() => Bangle.buzz(500), 1000);

View File

@ -1,6 +1,6 @@
{ "id": "chargent",
"name": "Charge Gently",
"version": "0.05",
"version": "0.07",
"description": "When charging, reminds you to disconnect the watch to prolong battery life.",
"icon": "icon.png",
"type": "bootloader",

View File

@ -1,2 +1,5 @@
0.01: New App!
0.02: added settings options to change date format
0.03: Remove un-needed font requirement, now outputs transparent image
0.04: Fix image after 0.03 regression
0.05: Remove duplicated day in calendar when format setting is 'dd MMM'

View File

@ -1,13 +1,11 @@
(function() {
require("Font4x8Numeric").add(Graphics);
var settings = require("Storage").readJSON("clkinfocal.json",1)||{};
settings.fmt = settings.fmt||"DDD";
var getDateString = function(dt) {
switch(settings.fmt) {
case "dd MMM":
return '' + dt.getDate() + ' ' + require("locale").month(dt,1).toUpperCase();
return require("locale").month(dt,1).toUpperCase();
case "DDD dd":
return require("locale").dow(dt,1).toUpperCase() + ' ' + dt.getDate();
default: // DDD
@ -22,6 +20,7 @@
get : () => {
let d = new Date();
let g = Graphics.createArrayBuffer(24,24,1,{msb:true});
g.transparent = 0;
g.drawImage(atob("FhgBDADAMAMP/////////////////////8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAPAAA8AADwAAP///////"),1,0);
g.setFont("6x15").setFontAlign(0,0).drawString(d.getDate(),11,17);
return {

View File

@ -1,6 +1,6 @@
{ "id": "clkinfocal",
"name": "Calendar Clockinfo",
"version":"0.02",
"version":"0.05",
"description": "For clocks that display 'clockinfo' (messages that can be cycled through using the clock_info module) this displays the day of the month in the icon, and the weekday. There is also a settings menu to select the format of the text",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],

View File

@ -91,7 +91,7 @@
};
var info = {
name: "Gps",
name: "GPS",
items: [
{
name: "gridref",

View File

@ -1,5 +1,5 @@
/**
*
*
* A module of Geo functions for use with gps fixes
*
* let geo = require("geotools");
@ -71,7 +71,7 @@ OsGridRef.latLongToOsGrid = function(point) {
*
*/
function to_map_ref(digits, easting, northing) {
if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`); // eslint-disable-line comma-spacing
if (![ 0,2,4,6,8,10,12,14,16 ].includes(Number(digits))) throw new RangeError(`invalid precision '${digits}'`);
let e = easting;
let n = northing;
@ -108,7 +108,7 @@ function to_map_ref(digits, easting, northing) {
}
/**
*
*
* Module exports section, example code below
*
* let geo = require("geotools");

View File

@ -7,3 +7,7 @@
0.06: When >1 clockinfo, swiping one back tries to ensure they don't display the same thing
0.07: Developer tweak: clkinfo load errors are emitted
0.08: Pass options to show(), hide() and run(), and add focus() and blur() item methods
0.09: Save clkinfo settings on kill and remove
0.10: Fix focus bug when changing focus between two clock infos
0.11: Prepend swipe listener if possible
0.12: Add drawFilledImage to allow drawing icons with a separately coloured middle

View File

@ -224,6 +224,13 @@ exports.addInteractive = function(menu, options) {
options.menuB = b;
}
}
const save = () => {
// save the currently showing clock_info
const settings = exports.loadSettings();
settings.apps[appName] = {a:options.menuA, b:options.menuB};
require("Storage").writeJSON("clock_info.json",settings);
};
E.on("kill", save);
if (options.menuA===undefined) options.menuA = 0;
if (options.menuB===undefined) options.menuB = Math.min(exports.loadCount, menu[options.menuA].items.length)-1;
@ -276,17 +283,13 @@ exports.addInteractive = function(menu, options) {
oldMenuItem.removeAllListeners("draw");
menuShowItem(menu[options.menuA].items[options.menuB]);
}
// save the currently showing clock_info
let settings = exports.loadSettings();
settings.apps[appName] = {a:options.menuA,b:options.menuB};
require("Storage").writeJSON("clock_info.json",settings);
// On 2v18+ firmware we can stop other event handlers from being executed since we handled this
E.stopEventPropagation&&E.stopEventPropagation();
}
Bangle.on("swipe",swipeHandler);
if (Bangle.prependListener) {Bangle.prependListener("swipe",swipeHandler);} else {Bangle.on("swipe",swipeHandler);}
const blur = () => {
options.focus=false;
delete Bangle.CLKINFO_FOCUS;
Bangle.CLKINFO_FOCUS--;
const itm = menu[options.menuA].items[options.menuB];
let redraw = true;
if (itm.blur && itm.blur(options) === false)
@ -295,7 +298,7 @@ exports.addInteractive = function(menu, options) {
};
const focus = () => {
let redraw = true;
Bangle.CLKINFO_FOCUS=true;
Bangle.CLKINFO_FOCUS = (0 | Bangle.CLKINFO_FOCUS) + 1;
if (!options.focus) {
options.focus=true;
const itm = menu[options.menuA].items[options.menuB];
@ -333,10 +336,12 @@ exports.addInteractive = function(menu, options) {
menuShowItem(menu[options.menuA].items[options.menuB]);
// return an object with info that can be used to remove the info
options.remove = function() {
save();
E.removeListener("kill", save);
Bangle.removeListener("swipe",swipeHandler);
if (touchHandler) Bangle.removeListener("touch",touchHandler);
if (lockHandler) Bangle.removeListener("lock", lockHandler);
delete Bangle.CLKINFO_FOCUS;
Bangle.CLKINFO_FOCUS--;
menuHideItem(menu[options.menuA].items[options.menuB]);
exports.loadCount--;
delete exports.clockInfos[options.index];
@ -366,6 +371,46 @@ exports.addInteractive = function(menu, options) {
return options;
};
/* clockinfos usually return a 24x24 image. This draws that image but
recolors it such that it is transparent, with the middle of the image as background
and the image itself as foreground. options is passed to g.drawImage */
exports.drawFilledImage = function(img,x,y,options) {
if (!img) return;
if (!g.floodFill/*2v18+*/) return g.drawImage(img,x,y,options);
let gfx = exports.imgGfx;
if (!gfx) {
gfx = exports.imgGfx = Graphics.createArrayBuffer(26, 26, 2, {msb:true});
gfx.transparent = 3;
gfx.palette = new Uint16Array([g.theme.bg, g.theme.fg, g.toColor("#888"), g.toColor("#888")]);
}
/* img is (usually) a black and white transparent image. But we really would like the bits in
the middle of it to be white. So what we do is we draw a slightly bigger rectangle in white,
draw the image, and then flood-fill the rectangle back to the background color. floodFill
was only added in 2v18 so we have to check for it and fallback if not. */
gfx.clear(1).setColor(1).drawImage(img, 1,1).floodFill(0,0,3);
var scale = (options && options.scale) || 1;
return g.drawImage(gfx, x-scale,y-scale,options);
};
/* clockinfos usually return a 24x24 image. This creates a 26x26 gfx of the image but
recolors it such that it is transparent, with the middle and border of the image as background
and the image itself as foreground. options is passed to g.drawImage */
exports.drawBorderedImage = function(img,x,y,options) {
if (!img) return;
if (!g.floodFill/*2v18+*/) return g.drawImage(img,x,y,options);
let gfx = exports.imgGfxB;
if (!gfx) {
gfx = exports.imgGfxB = Graphics.createArrayBuffer(28, 28, 2, {msb:true});
gfx.transparent = 3;
gfx.palette = new Uint16Array([g.theme.bg, g.theme.fg, g.theme.bg/*border*/, g.toColor("#888")]);
}
gfx.clear(1).setColor(2).drawImage(img, 1,1).drawImage(img, 3,1).drawImage(img, 1,3).drawImage(img, 3,3); // border
gfx.setColor(1).drawImage(img, 2,2); // main image
gfx.floodFill(27,27,3); // flood fill edge to transparent
var o = ((options && options.scale) || 1)*2;
return g.drawImage(gfx, x-o,y-o,options);
};
// Code for testing (plots all elements from first list)
/*
g.clear();

View File

@ -1,11 +1,11 @@
{ "id": "clock_info",
"name": "Clock Info Module",
"shortName": "Clock Info",
"version":"0.08",
"version":"0.12",
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
"icon": "app.png",
"type": "module",
"tags": "clkinfo",
"tags": "clkinfo,clockinfo",
"supports" : ["BANGLEJS2"],
"provides_modules" : ["clock_info"],
"readme": "README.md",

View File

@ -5,4 +5,6 @@
0.05: Improved colors (connected vs disconnected)
0.06: Tell clock widgets to hide.
0.07: Convert Yes/No On/Off in settings to checkboxes
0.08: Fixed typo in settings.js for DRAGDOWN to make option work
0.08: Fixed typo in settings.js for DRAGDOWN to make option work
0.09: You can now back out of the calendar using the button
0.10: Fix linter warnings

View File

@ -7,23 +7,24 @@ I know that it seems redundant because there already **is** a *time&cal*-app, bu
|:--:|:-|
|![locked screen](screenshot.png)|locked: triggers only one minimal update/min|
|![unlocked screen](screenshot2.png)|unlocked: smaller clock, but with seconds|
|![big calendar](screenshot3.png)|swipe up for big calendar, (up down to scroll, left/right to exit)|
|![big calendar](screenshot3.png)|swipe up for big calendar<br>⬆️/⬇️ to scroll<br> ⬅️/➡️ to exit|
## Configurable Features
- Number of calendar rows (weeks)
- Buzz on connect/disconnect (I know, this should be an extra widget, but for now, it is included)
- Buzz on connect/disconnect (feel free to disable and use a widget)
- Clock Mode (24h/12h). (No am/pm indicator)
- First day of the week
- Red Saturday/Sunday
- Swipe/Drag gestures to launch features or apps.
## Auto detects your message/music apps:
- swiping down will search your files for an app with the string "message" in its filename and launch it. (configurable)
- swiping right will search your files for an app with the string "music" in its filename and launch it. (configurable)
## Integrated swipe launcher: (Configure in Settings)
- ⬇️ (down) will search your files for an app with the string "**message**"
- ➡️ (right) will search your files for an app with the string "**music**"
- ⬅️ (left) will search your files for an app with the string "**agenda**"
- ⬆️ (up) will show the **internal full calendar**
## Feedback
The clock works for me in a 24h/MondayFirst/WeekendFree environment but is not well-tested with other settings.
So if something isn't working, please tell me: https://github.com/foostuff/BangleApps/issues
If something isn't working, please tell me: https://github.com/Stuff-etc/BangleApps/issues (I moved my github repo)
## Planned features:
- Internal lightweight music control, because switching apps has a loading time.

View File

@ -24,15 +24,25 @@ const DEBUG = false;
var state = "watch";
var monthOffset = 0;
// FIXME: These variables should maybe be defined inside relevant functions below. The linter complained they were not defined (i.e. they were added to global scope if I understand correctly).
let dayInterval;
let secondInterval;
let minuteInterval;
let newmonth;
let bottomrightY;
let bottomrightX;
let rMonth;
let dimSeconds;
/*
* Calendar features
*/
function drawFullCalendar(monthOffset) {
addMonths = function (_d, _am) {
var ay = 0, m = _d.getMonth(), y = _d.getFullYear();
const addMonths = function (_d, _am) {
let ay = 0, m = _d.getMonth(), y = _d.getFullYear();
while ((m + _am) > 11) { ay++; _am -= 12; }
while ((m + _am) < 0) { ay--; _am += 12; }
n = new Date(_d.getTime());
let n = new Date(_d.getTime());
n.setMonth(m + _am);
n.setFullYear(y + ay);
return n;
@ -45,10 +55,10 @@ function drawFullCalendar(monthOffset) {
if (typeof dayInterval !== "undefined") clearTimeout(dayInterval);
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
if (typeof minuteInterval !== "undefined") clearTimeout(minuteInterval);
d = addMonths(Date(), monthOffset);
tdy = Date().getDate() + "." + Date().getMonth();
var d = addMonths(Date(), monthOffset);
let tdy = Date().getDate() + "." + Date().getMonth();
newmonth = false;
c_y = 0;
let c_y = 0;
g.reset();
g.setBgColor(0);
g.clear();
@ -60,8 +70,8 @@ function drawFullCalendar(monthOffset) {
rD.setDate(rD.getDate() - dow);
var rDate = rD.getDate();
bottomrightY = c_y - 3;
clrsun = s.REDSUN ? '#f00' : '#fff';
clrsat = s.REDSUN ? '#f00' : '#fff';
let clrsun = s.REDSUN ? '#f00' : '#fff';
let clrsat = s.REDSUN ? '#f00' : '#fff';
var fg = [clrsun, '#fff', '#fff', '#fff', '#fff', '#fff', clrsat];
for (var y = 1; y <= 11; y++) {
bottomrightY += CELL_H;
@ -90,7 +100,7 @@ function caldrawMonth(rDate, c, m, rD) {
g.setColor(c);
g.setFont("Vector", 18);
g.setFontAlign(-1, 1, 1);
drawyear = ((rMonth % 11) == 0) ? String(rD.getFullYear()).substr(-2) : "";
let drawyear = ((rMonth % 11) == 0) ? String(rD.getFullYear()).substr(-2) : "";
g.drawString(m + drawyear, bottomrightX, bottomrightY - CELL_H, 1);
newmonth = false;
}
@ -124,7 +134,7 @@ function drawMinutes() {
var d = new Date();
var hours = s.MODE24 ? d.getHours().toString().padStart(2, ' ') : ((d.getHours() + 24) % 12 || 12).toString().padStart(2, ' ');
var minutes = d.getMinutes().toString().padStart(2, '0');
var textColor = NRF.getSecurityStatus().connected ? '#99f' : '#fff';
var textColor = NRF.getSecurityStatus().connected ? '#fff' : '#f00';
var size = 50;
var clock_x = (w - 20) / 2;
if (dimSeconds) {
@ -156,7 +166,7 @@ function drawSeconds() {
}
function drawWatch() {
if (DEBUG) console.log("CALENDAR");
if (DEBUG) console.log("DRAWWATCH");
monthOffset = 0;
state = "watch";
var d = new Date();
@ -197,6 +207,7 @@ function drawWatch() {
if (DEBUG) console.log("Next Day:" + (nextday / 3600));
if (typeof dayInterval !== "undefined") clearTimeout(dayInterval);
dayInterval = setTimeout(drawWatch, nextday * 1000);
if (DEBUG) console.log("ended DRAWWATCH. next refresh in " + nextday + "s");
}
function BTevent() {
@ -211,8 +222,12 @@ function action(a) {
g.reset();
if (typeof secondInterval !== "undefined") clearTimeout(secondInterval);
if (DEBUG) console.log("action:" + a);
state = "unknown";
console.log("state -> unknown");
let l;
switch (a) {
case "[ignore]":
drawWatch();
break;
case "[calend.]":
drawFullCalendar();
@ -229,6 +244,12 @@ function action(a) {
load(l[0]);
} else E.showAlert("Message app not found", "Not found").then(drawWatch);
break;
case "[AI:agenda]":
l = require("Storage").list(RegExp("agenda.*app.js"));
if (l.length > 0) {
load(l[0]);
} else E.showAlert("Agenda app not found", "Not found").then(drawWatch);
break;
default:
l = require("Storage").list(RegExp(a + ".app.js"));
if (l.length > 0) {
@ -276,7 +297,6 @@ function input(dir) {
drawWatch();
}
break;
}
}
@ -309,3 +329,10 @@ NRF.on('disconnect', BTevent);
dimSeconds = Bangle.isLocked();
drawWatch();
setWatch(function() {
if (state == "watch") {
Bangle.showLauncher()
} else if (state == "calendar") {
drawWatch();
}
}, BTN1, {repeat:true, edge:"falling"});

View File

@ -1,7 +1,7 @@
{
"id": "clockcal",
"name": "Clock & Calendar",
"version": "0.08",
"version": "0.10",
"description": "Clock with Calendar",
"readme":"README.md",
"icon": "app.png",

View File

@ -1,6 +1,6 @@
(function (back) {
var FILE = "clockcal.json";
defaults={
const defaults={
CAL_ROWS: 4, //number of calendar rows.(weeks) Shouldn't exceed 5 when using widgets.
BUZZ_ON_BT: true, //2x slow buzz on disconnect, 2x fast buzz on connect. Will be extra widget eventually
MODE24: true, //24h mode vs 12h mode
@ -9,19 +9,19 @@
REDSAT: true, // Use red color for saturday?
DRAGDOWN: "[AI:messg]",
DRAGRIGHT: "[AI:music]",
DRAGLEFT: "[ignore]",
DRAGLEFT: "[AI:agenda]",
DRAGUP: "[calend.]"
};
settings = Object.assign(defaults, require('Storage').readJSON(FILE, true) || {});
let settings = Object.assign(defaults, require('Storage').readJSON(FILE, true) || {});
actions = ["[ignore]","[calend.]","[AI:music]","[AI:messg]"];
let actions = ["[ignore]","[calend.]","[AI:music]","[AI:messg]","[AI:agenda]"];
require("Storage").list(RegExp(".app.js")).forEach(element => actions.push(element.replace(".app.js","")));
function writeSettings() {
require('Storage').writeJSON(FILE, settings);
}
menu = {
const menu = {
"": { "title": "Clock & Calendar" },
"< Back": () => back(),
'Buzz(dis)conn.?': {

View File

@ -1,2 +1,3 @@
0.01: 1st ver,RGB565 and RGB888 colors in a common UI/UX
0.02: Minor code improvements
0.03: Minor code improvements

View File

@ -35,22 +35,22 @@ var v_model=process.env.BOARD;
var v_color_text='#FB0E01';
var v_color_statictxt='#e56e06'; //orange RGB format rrggbb
//RGB565 requires only 16 (5+6+5) bits/2 bytes
var a_colors_str= Array('White RGB565 0x','Orange','DarkGreen','Yellow',
var a_colors_str= ['White RGB565 0x','Orange','DarkGreen','Yellow',
'Maroon','Blue','green','Purple',
'cyan','olive','DarkCyan','DarkGrey',
'Navy','Red','Magenta','GreenYellow',
'Blush RGB888','pure red','Orange','Grey green',
'D. grey','Almond','Amber','Bone',
'Canary','Aero blue','Camel','Baby pink',
'Y.Corn','Cultured','Eigengrau','Citrine');
var a_colors= Array(0xFFFF,0xFD20,0x03E0,0xFFE0,
'Y.Corn','Cultured','Eigengrau','Citrine'];
var a_colors= [0xFFFF,0xFD20,0x03E0,0xFFE0,
0x7800,0x001F,0x07E0,0x780F,
0x07FF,0x7BE0,0x03EF,0x7BEF,
0x000F,0xF800,0xF81F,0xAFE5,
'#DE5D83','#FB0E01','#E56E06','#7E795C',
'#404040','#EFDECD','#FFBF00','#E3DAC9',
'#FFFF99','#C0E8D5','#C19A6B','#F4C2C2',
'#FBEC5D','#F5F5F5','#16161D','#E4D00A');
'#FBEC5D','#F5F5F5','#16161D','#E4D00A'];
var v_color_lines=0xFFFF; //White hex format

View File

@ -2,7 +2,7 @@
"id": "color_catalog",
"name": "Colors Catalog",
"shortName": "Colors Catalog",
"version": "0.02",
"version": "0.03",
"description": "Displays RGB565 and RGB888 colors, its name and code in screen.",
"icon": "app.png",
"tags": "Color,input,buttons,touch,UI",

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: Minor code improvements
0.03: Minor code improvements
0.04: Allow calling contacts from the app, Refactoring

View File

@ -2,108 +2,102 @@
var Layout = require("Layout");
//const W = g.getWidth();
//const H = g.getHeight();
var wp = require('Storage').readJSON("contacts.json", true) || [];
// Use this with corrupted contacts
//var wp = [];
var key; /* Shared between functions, typically wp name */
function writeContact() {
function writeContacts() {
require('Storage').writeJSON("contacts.json", wp);
}
function callNumber (number) {
Bluetooth.println(JSON.stringify({
t:"intent",
target:"activity",
action:"android.intent.action.CALL",
flags:["FLAG_ACTIVITY_NEW_TASK"],
categories:["android.intent.category.DEFAULT"],
data: 'tel:' + number,
}))
}
function mainMenu() {
var menu = {
"< Back" : Bangle.load
};
if (Object.keys(wp).length==0) Object.assign(menu, {"NO Contacts":""});
else for (let id in wp) {
let i = id;
menu[wp[id]["name"]]=()=>{ decode(i); };
if (!wp.length) {
menu['No Contacts'] = () => {};
} else {
for (const e of wp) {
const closureE = e;
menu[e.name] = () => showContact(closureE);
}
}
menu["Add"]=addCard;
menu["Remove"]=removeCard;
menu["Add"] = addContact;
menu["Remove"] = removeContact;
g.clear();
E.showMenu(menu);
}
function decode(pin) {
var i = wp[pin];
var l = i["name"] + "\n" + i["number"];
var la = new Layout ({
type:"v", c: [
{type:"txt", font:"10%", pad:1, fillx:1, filly:1, label: l},
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label:"OK", cb:l=>{mainMenu();}}
], lazy:true});
function showContact(i) {
g.clear();
la.render();
(new Layout ({
type:"v",
c: [
{type:"txt", font:"10%", pad:1, fillx:1, filly:1, label: i["name"] + "\n" + i["number"]},
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "Call", cb: l => callNumber(i['number'])},
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "Back to list", cb: mainMenu}
],
lazy:true
})).render();
}
function showNumpad(text, key_, callback) {
key = key_;
E.showMenu();
function addDigit(digit) {
key+=digit;
if (1) {
l = text[key.length];
switch (l) {
case '.': case ' ': case "'":
key+=l;
break;
case 'd': case 'D': default:
break;
}
function showNumpad() {
return new Promise((resolve, reject) => {
let number = ''
E.showMenu();
function addDigit(digit) {
number += digit;
Bangle.buzz(20);
update();
}
Bangle.buzz(20);
update();
}
function update() {
g.reset();
g.clearRect(0,0,g.getWidth(),23);
s = key + text.substr(key.length, 999);
g.setFont("Vector:24").setFontAlign(1,0).drawString(s,g.getWidth(),12);
}
const ds="12%";
var numPad = new Layout ({
type:"v", c: [{
type:"v", c: [
{type:"", height:24},
{type:"h",filly:1, c: [
{type:"btn", font:ds, width:58, label:"7", cb:l=>{addDigit("7");}},
{type:"btn", font:ds, width:58, label:"8", cb:l=>{addDigit("8");}},
{type:"btn", font:ds, width:58, label:"9", cb:l=>{addDigit("9");}}
]},
{type:"h",filly:1, c: [
{type:"btn", font:ds, width:58, label:"4", cb:l=>{addDigit("4");}},
{type:"btn", font:ds, width:58, label:"5", cb:l=>{addDigit("5");}},
{type:"btn", font:ds, width:58, label:"6", cb:l=>{addDigit("6");}}
]},
{type:"h",filly:1, c: [
{type:"btn", font:ds, width:58, label:"1", cb:l=>{addDigit("1");}},
{type:"btn", font:ds, width:58, label:"2", cb:l=>{addDigit("2");}},
{type:"btn", font:ds, width:58, label:"3", cb:l=>{addDigit("3");}}
]},
{type:"h",filly:1, c: [
{type:"btn", font:ds, width:58, label:"0", cb:l=>{addDigit("0");}},
{type:"btn", font:ds, width:58, label:"C", cb:l=>{key=key.slice(0,-1); update();}},
{type:"btn", font:ds, width:58, id:"OK", label:"OK", cb:callback}
function removeDigit() {
number = number.slice(0, -1);
Bangle.buzz(20);
update();
}
function update() {
g.reset();
g.clearRect(0,0,g.getWidth(),23);
g.setFont("Vector:24").setFontAlign(1,0).drawString(number, g.getWidth(),12);
}
const ds="12%";
const digitBtn = (digit) => ({type:"btn", font:ds, width:58, label:digit, cb:l=>{addDigit(digit);}});
var numPad = new Layout ({
type:"v", c: [{
type:"v", c: [
{type:"", height:24},
{type:"h",filly:1, c: [digitBtn("1"), digitBtn("2"), digitBtn("3")]},
{type:"h",filly:1, c: [digitBtn("4"), digitBtn("5"), digitBtn("6")]},
{type:"h",filly:1, c: [digitBtn("7"), digitBtn("8"), digitBtn("9")]},
{type:"h",filly:1, c: [
{type:"btn", font:ds, width:58, label:"C", cb: removeDigit},
digitBtn('0'),
{type:"btn", font:ds, width:58, id:"OK", label:"OK", cb: l => resolve(number)}
]}
]}
]}
], lazy:true});
g.clear();
numPad.render();
update();
], lazy:true});
g.clear();
numPad.render();
update();
});
}
function removeCard() {
function removeContact() {
var menu = {
"" : {title : "Select Contact"},
"< Back" : mainMenu
};
if (Object.keys(wp).length==0) Object.assign(menu, {"No Contacts":""});
if (wp.length===0) Object.assign(menu, {"No Contacts":""});
else {
wp.forEach((val, card) => {
const name = wp[card].name;
@ -116,7 +110,7 @@ function removeCard() {
{type:"h", c: [
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: "YES", cb:l=>{
wp.splice(card, 1);
writeContact();
writeContacts();
mainMenu();
}},
{type:"btn", font:"15%", pad:1, fillx:1, filly:1, label: " NO", cb:l=>{mainMenu();}}
@ -130,54 +124,42 @@ function removeCard() {
E.showMenu(menu);
}
function askPosition(callback) {
showNumpad("dddDDDddd", "", function() {
callback(key, "");
});
}
function createContact(lat, name) {
let n = {};
n["name"] = name;
n["number"] = lat;
wp.push(n);
print("add -- contacts", wp);
writeContact();
}
function addCardName2(key) {
function addNewContact(name) {
g.clear();
askPosition(function(lat, lon) {
print("position -- ", lat, lon);
createContact(lat, result);
showNumpad().then((number) => {
wp.push({name: name, number: number});
writeContacts();
mainMenu();
});
})
}
function addCardName(key) {
result = key;
if (wp[result]!=undefined) {
E.showMenu();
var alreadyExists = new Layout (
{type:"v", c: [
{type:"txt", font:Math.min(15,100/result.length)+"%", pad:1, fillx:1, filly:1, label:result},
{type:"txt", font:"12%", pad:1, fillx:1, filly:1, label:"already exists."},
{type:"h", c: [
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "REPLACE", cb:l=>{ addCardName2(key); }},
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "CANCEL", cb:l=>{mainMenu();}}
]}
], lazy:true});
g.clear();
alreadyExists.render();
function tryAddContact(name) {
if (wp.filter((e) => e.name === name).length) {
E.showMenu();
var alreadyExists = new Layout (
{type:"v", c: [
{type:"txt", font:Math.min(15,100/name.length)+"%", pad:1, fillx:1, filly:1, label:name},
{type:"txt", font:"12%", pad:1, fillx:1, filly:1, label:"already exists."},
{type:"h", c: [
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "REPLACE", cb:l=>{ addNewContact(name); }},
{type:"btn", font:"10%", pad:1, fillx:1, filly:1, label: "CANCEL", cb:l=>{mainMenu();}}
]}
], lazy:true});
g.clear();
alreadyExists.render();
return;
}
addCardName2(key);
addNewContact(name);
}
function addCard() {
require("textinput").input({text:""}).then(result => {
if (result != "") {
addCardName(result);
function addContact() {
require("textinput").input({text:""}).then(name => {
if (name !== "") {
tryAddContact(name);
} else
mainMenu();
});

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