Merge branch 'master' into message_buzz_pattern_for_calls
|
@ -11,3 +11,4 @@
|
|||
0.10: Fix SMS bug
|
||||
0.12: Use default Bangle formatter for booleans
|
||||
0.13: Added Bangle.http function (see Readme file for more info)
|
||||
0.14: Fix timeout of http function not beeing cleaned up
|
||||
|
|
|
@ -122,14 +122,14 @@
|
|||
"http":function() {
|
||||
//get the promise and call the promise resolve
|
||||
if (Bangle.httpRequest === undefined) return;
|
||||
var objID=Bangle.httpRequest[event.id];
|
||||
if (objID === undefined) return; //already timedout or wrong id
|
||||
var request=Bangle.httpRequest[event.id];
|
||||
if (request === undefined) return; //already timedout or wrong id
|
||||
delete Bangle.httpRequest[event.id];
|
||||
clearInterval(objID.t); //t = timeout variable
|
||||
clearTimeout(request.t); //t = timeout variable
|
||||
if(event.err!==undefined) //if is error
|
||||
objID.j(event.err); //r = reJect function
|
||||
request.j(event.err); //r = reJect function
|
||||
else
|
||||
objID.r(event); //r = resolve function
|
||||
request.r(event); //r = resolve function
|
||||
}
|
||||
};
|
||||
var h = HANDLERS[event.t];
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.13",
|
||||
"version": "0.14",
|
||||
"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",
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
...
|
||||
0.02: First update with ChangeLog Added
|
|
@ -23,3 +23,9 @@
|
|||
0.08: Allow scanning for devices in settings
|
||||
0.09: Misc Fixes and improvements (https://github.com/espruino/BangleApps/pull/1655)
|
||||
0.10: Use default Bangle formatter for booleans
|
||||
0.11: App now shows status info while connecting
|
||||
Fixes to allow cached BluetoothRemoteGATTCharacteristic to work with 2v14.14 onwards (>1 central)
|
||||
0.12: Fix HRM fallback handling
|
||||
Use default boolean formatter in custom menu and directly apply config if useful
|
||||
Allow recording unmodified internal HR
|
||||
Better connection retry handling
|
||||
|
|
|
@ -19,7 +19,14 @@ Just install the app, then install an app that uses the heart rate monitor.
|
|||
Once installed you will have to go into this app's settings while your heart rate monitor
|
||||
is available for bluetooth pairing and scan for devices.
|
||||
|
||||
**To disable this and return to normal HRM, uninstall the app**
|
||||
**To disable this and return to normal HRM, uninstall the app or change the settings**
|
||||
|
||||
### Modes
|
||||
|
||||
* Off - Internal HRM is used, no attempt on connecting to BT HRM.
|
||||
* Default - Replaces internal HRM with BT HRM and falls back to internal HRM if no valid measurements received.
|
||||
* Both - The BT HRM needs to be started explicitly by an app that wants to use it. BT HRM has its own event and is completely separated from the internal HRM. Apps not supporting the BT HRM will not see the BT HRM measurements.
|
||||
* Custom - Combine low level settings as you see fit.
|
||||
|
||||
## Compatible Heart Rate Monitors
|
||||
|
||||
|
@ -35,6 +42,10 @@ So far it has been tested on:
|
|||
* Polar OH1
|
||||
* Wahoo TICKR X 2
|
||||
|
||||
## Recorder plugin
|
||||
|
||||
The recorder plugin can record the BT HRM event (blue) and the original unchanged HRM event (green). This is mainly useful for debugging purposes or comparing the BT with the internal HRM, as the resulting "merged" HRM can be recordet using the default HRM recorder.
|
||||
|
||||
## Internals
|
||||
|
||||
This replaces `Bangle.setHRMPower` with its own implementation.
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
);
|
||||
|
||||
var log = function(text, param){
|
||||
if (global.showStatusInfo)
|
||||
showStatusInfo(text);
|
||||
if (settings.debuglog){
|
||||
var logline = new Date().toISOString() + " - " + text;
|
||||
if (param){
|
||||
logline += " " + JSON.stringify(param);
|
||||
}
|
||||
if (param) logline += ": " + JSON.stringify(param);
|
||||
print(logline);
|
||||
}
|
||||
};
|
||||
|
@ -30,7 +30,7 @@
|
|||
};
|
||||
|
||||
var addNotificationHandler = function(characteristic) {
|
||||
log("Setting notification handler: " + supportedCharacteristics[characteristic.uuid].handler);
|
||||
log("Setting notification handler"/*supportedCharacteristics[characteristic.uuid].handler*/);
|
||||
characteristic.on('characteristicvaluechanged', (ev) => supportedCharacteristics[characteristic.uuid].handler(ev.target.value));
|
||||
};
|
||||
|
||||
|
@ -61,7 +61,8 @@
|
|||
writeCache(cache);
|
||||
};
|
||||
|
||||
var characteristicsFromCache = function() {
|
||||
var characteristicsFromCache = function(device) {
|
||||
var service = { device : device }; // fake a BluetoothRemoteGATTService
|
||||
log("Read cached characteristics");
|
||||
var cache = getCache();
|
||||
if (!cache.characteristics) return [];
|
||||
|
@ -75,6 +76,7 @@
|
|||
r.properties = {};
|
||||
r.properties.notify = cached.notify;
|
||||
r.properties.read = cached.read;
|
||||
r.service = service;
|
||||
addNotificationHandler(r);
|
||||
log("Restored characteristic: ", r);
|
||||
restored.push(r);
|
||||
|
@ -92,13 +94,24 @@
|
|||
"0x180f", // Battery
|
||||
];
|
||||
|
||||
var bpmTimeout;
|
||||
|
||||
var supportedCharacteristics = {
|
||||
"0x2a37": {
|
||||
//Heart rate measurement
|
||||
active: false,
|
||||
handler: function (dv){
|
||||
var flags = dv.getUint8(0);
|
||||
|
||||
var 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);
|
||||
if (supportedCharacteristics["0x2a37"].active) stopFallback();
|
||||
if (bpmTimeout) clearTimeout(bpmTimeout);
|
||||
bpmTimeout = setTimeout(()=>{
|
||||
supportedCharacteristics["0x2a37"].active = false;
|
||||
startFallback();
|
||||
}, 3000);
|
||||
|
||||
var sensorContact;
|
||||
|
||||
|
@ -141,8 +154,8 @@
|
|||
src: "bthrm"
|
||||
};
|
||||
|
||||
log("Emitting HRM: ", repEvent);
|
||||
Bangle.emit("HRM", repEvent);
|
||||
log("Emitting HRM", repEvent);
|
||||
Bangle.emit("HRM_int", repEvent);
|
||||
}
|
||||
|
||||
var newEvent = {
|
||||
|
@ -155,7 +168,7 @@
|
|||
if (battery) newEvent.battery = battery;
|
||||
if (sensorContact) newEvent.contact = sensorContact;
|
||||
|
||||
log("Emitting BTHRM: ", newEvent);
|
||||
log("Emitting BTHRM", newEvent);
|
||||
Bangle.emit("BTHRM", newEvent);
|
||||
}
|
||||
},
|
||||
|
@ -200,6 +213,10 @@
|
|||
};
|
||||
|
||||
if (settings.enabled){
|
||||
Bangle.isBTHRMActive = function (){
|
||||
return supportedCharacteristics["0x2a37"].active;
|
||||
};
|
||||
|
||||
Bangle.isBTHRMOn = function(){
|
||||
return (Bangle._PWR && Bangle._PWR.BTHRM && Bangle._PWR.BTHRM.length > 0);
|
||||
};
|
||||
|
@ -210,24 +227,28 @@
|
|||
}
|
||||
|
||||
if (settings.replace){
|
||||
var origIsHRMOn = Bangle.isHRMOn;
|
||||
Bangle.origIsHRMOn = Bangle.isHRMOn;
|
||||
|
||||
Bangle.isHRMOn = function() {
|
||||
if (settings.enabled && !settings.replace){
|
||||
return origIsHRMOn();
|
||||
return Bangle.origIsHRMOn();
|
||||
} else if (settings.enabled && settings.replace){
|
||||
return Bangle.isBTHRMOn();
|
||||
}
|
||||
return origIsHRMOn() || Bangle.isBTHRMOn();
|
||||
return Bangle.origIsHRMOn() || Bangle.isBTHRMOn();
|
||||
};
|
||||
}
|
||||
|
||||
var clearRetryTimeout = function() {
|
||||
var clearRetryTimeout = function(resetTime) {
|
||||
if (currentRetryTimeout){
|
||||
log("Clearing timeout " + currentRetryTimeout);
|
||||
clearTimeout(currentRetryTimeout);
|
||||
currentRetryTimeout = undefined;
|
||||
}
|
||||
if (resetTime) {
|
||||
log("Resetting retry time");
|
||||
retryTime = initialRetryTime;
|
||||
}
|
||||
};
|
||||
|
||||
var retry = function() {
|
||||
|
@ -236,8 +257,8 @@
|
|||
if (!currentRetryTimeout){
|
||||
|
||||
var clampedTime = retryTime < 100 ? 100 : retryTime;
|
||||
|
||||
log("Set timeout for retry as " + clampedTime);
|
||||
|
||||
log("Set timeout for retry as " + clampedTime);
|
||||
clearRetryTimeout();
|
||||
currentRetryTimeout = setTimeout(() => {
|
||||
log("Retrying");
|
||||
|
@ -257,11 +278,11 @@
|
|||
var buzzing = false;
|
||||
var onDisconnect = function(reason) {
|
||||
log("Disconnect: " + reason);
|
||||
log("GATT: ", gatt);
|
||||
log("Characteristics: ", characteristics);
|
||||
retryTime = initialRetryTime;
|
||||
clearRetryTimeout();
|
||||
switchInternalHrm();
|
||||
log("GATT", gatt);
|
||||
log("Characteristics", characteristics);
|
||||
clearRetryTimeout(reason != "Connection Timeout");
|
||||
supportedCharacteristics["0x2a37"].active = false;
|
||||
startFallback();
|
||||
blockInit = false;
|
||||
if (settings.warnDisconnect && !buzzing){
|
||||
buzzing = true;
|
||||
|
@ -273,13 +294,13 @@
|
|||
};
|
||||
|
||||
var createCharacteristicPromise = function(newCharacteristic) {
|
||||
log("Create characteristic promise: ", newCharacteristic);
|
||||
log("Create characteristic promise", newCharacteristic);
|
||||
var 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){
|
||||
result = result.then(()=>{
|
||||
log("Reading data for " + JSON.stringify(newCharacteristic));
|
||||
log("Reading data", newCharacteristic);
|
||||
return newCharacteristic.readValue().then((data)=>{
|
||||
if (supportedCharacteristics[newCharacteristic.uuid] && supportedCharacteristics[newCharacteristic.uuid].handler) {
|
||||
supportedCharacteristics[newCharacteristic.uuid].handler(data);
|
||||
|
@ -289,8 +310,8 @@
|
|||
}
|
||||
if (newCharacteristic.properties.notify){
|
||||
result = result.then(()=>{
|
||||
log("Starting notifications for: ", newCharacteristic);
|
||||
var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started for ", newCharacteristic));
|
||||
log("Starting notifications", newCharacteristic);
|
||||
var startPromise = newCharacteristic.startNotifications().then(()=>log("Notifications started", newCharacteristic));
|
||||
if (settings.gracePeriodNotification > 0){
|
||||
log("Add " + settings.gracePeriodNotification + "ms grace period after starting notifications");
|
||||
startPromise = startPromise.then(()=>{
|
||||
|
@ -301,7 +322,7 @@
|
|||
return startPromise;
|
||||
});
|
||||
}
|
||||
return result.then(()=>log("Handled characteristic: ", newCharacteristic));
|
||||
return result.then(()=>log("Handled characteristic", newCharacteristic));
|
||||
};
|
||||
|
||||
var attachCharacteristicPromise = function(promise, characteristic) {
|
||||
|
@ -312,11 +333,11 @@
|
|||
};
|
||||
|
||||
var createCharacteristicsPromise = function(newCharacteristics) {
|
||||
log("Create characteristics promise: ", newCharacteristics);
|
||||
log("Create characteristics promis ", newCharacteristics);
|
||||
var result = Promise.resolve();
|
||||
for (var c of newCharacteristics){
|
||||
if (!supportedCharacteristics[c.uuid]) continue;
|
||||
log("Supporting characteristic: ", c);
|
||||
log("Supporting characteristic", c);
|
||||
characteristics.push(c);
|
||||
if (c.properties.notify){
|
||||
addNotificationHandler(c);
|
||||
|
@ -328,10 +349,10 @@
|
|||
};
|
||||
|
||||
var createServicePromise = function(service) {
|
||||
log("Create service promise: ", service);
|
||||
log("Create service promise", service);
|
||||
var result = Promise.resolve();
|
||||
result = result.then(()=>{
|
||||
log("Handling service: " + service.uuid);
|
||||
log("Handling service" + service.uuid);
|
||||
return service.getCharacteristics().then((c)=>createCharacteristicsPromise(c));
|
||||
});
|
||||
return result.then(()=>log("Handled service" + service.uuid));
|
||||
|
@ -368,7 +389,7 @@
|
|||
}
|
||||
|
||||
promise = promise.then((d)=>{
|
||||
log("Got device: ", d);
|
||||
log("Got device", d);
|
||||
d.on('gattserverdisconnected', onDisconnect);
|
||||
device = d;
|
||||
});
|
||||
|
@ -379,14 +400,14 @@
|
|||
});
|
||||
} else {
|
||||
promise = Promise.resolve();
|
||||
log("Reuse device: ", device);
|
||||
log("Reuse device", device);
|
||||
}
|
||||
|
||||
promise = promise.then(()=>{
|
||||
if (gatt){
|
||||
log("Reuse GATT: ", gatt);
|
||||
log("Reuse GATT", gatt);
|
||||
} else {
|
||||
log("GATT is new: ", gatt);
|
||||
log("GATT is new", gatt);
|
||||
characteristics = [];
|
||||
var cachedId = getCache().id;
|
||||
if (device.id !== cachedId){
|
||||
|
@ -404,7 +425,10 @@
|
|||
|
||||
promise = promise.then((gatt)=>{
|
||||
if (!gatt.connected){
|
||||
var connectPromise = gatt.connect(connectSettings);
|
||||
log("Connecting...");
|
||||
var connectPromise = gatt.connect(connectSettings).then(function() {
|
||||
log("Connected.");
|
||||
});
|
||||
if (settings.gracePeriodConnect > 0){
|
||||
log("Add " + settings.gracePeriodConnect + "ms grace period after connecting");
|
||||
connectPromise = connectPromise.then(()=>{
|
||||
|
@ -432,7 +456,7 @@
|
|||
|
||||
promise = promise.then(()=>{
|
||||
if (!characteristics || characteristics.length === 0){
|
||||
characteristics = characteristicsFromCache();
|
||||
characteristics = characteristicsFromCache(device);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -445,11 +469,11 @@
|
|||
});
|
||||
|
||||
characteristicsPromise = characteristicsPromise.then((services)=>{
|
||||
log("Got services:", 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);
|
||||
log("Supporting service", service.uuid);
|
||||
result = attachServicePromise(result, service);
|
||||
}
|
||||
if (settings.gracePeriodService > 0) {
|
||||
|
@ -473,7 +497,7 @@
|
|||
return promise.then(()=>{
|
||||
log("Connection established, waiting for notifications");
|
||||
characteristicsToCache(characteristics);
|
||||
clearRetryTimeout();
|
||||
clearRetryTimeout(true);
|
||||
}).catch((e) => {
|
||||
characteristics = [];
|
||||
log("Error:", e);
|
||||
|
@ -491,12 +515,14 @@
|
|||
isOn = Bangle._PWR.BTHRM.length;
|
||||
// so now we know if we're really on
|
||||
if (isOn) {
|
||||
switchFallback();
|
||||
if (!Bangle.isBTHRMConnected()) initBt();
|
||||
} else { // not on
|
||||
log("Power off for " + app);
|
||||
clearRetryTimeout(true);
|
||||
if (gatt) {
|
||||
if (gatt.connected){
|
||||
log("Disconnect with gatt: ", gatt);
|
||||
log("Disconnect with gatt", gatt);
|
||||
try{
|
||||
gatt.disconnect().then(()=>{
|
||||
log("Successful disconnect");
|
||||
|
@ -511,7 +537,33 @@
|
|||
}
|
||||
};
|
||||
|
||||
var origSetHRMPower = Bangle.setHRMPower;
|
||||
if (settings.replace){
|
||||
Bangle.on("HRM", (e) => {
|
||||
e.modified = true;
|
||||
Bangle.emit("HRM_int", e);
|
||||
});
|
||||
|
||||
Bangle.origOn = Bangle.on;
|
||||
Bangle.on = function(name, callback) {
|
||||
if (name == "HRM") {
|
||||
Bangle.origOn("HRM_int", callback);
|
||||
} else {
|
||||
Bangle.origOn(name, callback);
|
||||
}
|
||||
};
|
||||
|
||||
Bangle.origRemoveListener = Bangle.removeListener;
|
||||
Bangle.removeListener = function(name, callback) {
|
||||
if (name == "HRM") {
|
||||
Bangle.origRemoveListener("HRM_int", callback);
|
||||
} else {
|
||||
Bangle.origRemoveListener(name, callback);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Bangle.origSetHRMPower = Bangle.setHRMPower;
|
||||
|
||||
if (settings.startWithHrm){
|
||||
|
||||
|
@ -521,40 +573,54 @@
|
|||
Bangle.setBTHRMPower(isOn, app);
|
||||
}
|
||||
if ((settings.enabled && !settings.replace) || !settings.enabled){
|
||||
origSetHRMPower(isOn, app);
|
||||
Bangle.origSetHRMPower(isOn, app);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
var fallbackInterval;
|
||||
var fallbackActive = false;
|
||||
var inSwitch = false;
|
||||
|
||||
var switchInternalHrm = function() {
|
||||
if (settings.allowFallback && !fallbackInterval){
|
||||
log("Fallback to HRM enabled");
|
||||
origSetHRMPower(1, "bthrm_fallback");
|
||||
fallbackInterval = setInterval(()=>{
|
||||
if (Bangle.isBTHRMConnected()){
|
||||
origSetHRMPower(0, "bthrm_fallback");
|
||||
clearInterval(fallbackInterval);
|
||||
fallbackInterval = undefined;
|
||||
log("Fallback to HRM disabled");
|
||||
}
|
||||
}, settings.fallbackTimeout);
|
||||
var stopFallback = function(){
|
||||
if (fallbackActive){
|
||||
Bangle.origSetHRMPower(0, "bthrm_fallback");
|
||||
fallbackActive = false;
|
||||
log("Fallback to HRM disabled");
|
||||
}
|
||||
};
|
||||
|
||||
var startFallback = function(){
|
||||
if (!fallbackActive && settings.allowFallback) {
|
||||
fallbackActive = true;
|
||||
Bangle.origSetHRMPower(1, "bthrm_fallback");
|
||||
log("Fallback to HRM enabled");
|
||||
}
|
||||
};
|
||||
|
||||
var switchFallback = function() {
|
||||
log("Check falling back to HRM");
|
||||
if (!inSwitch){
|
||||
inSwitch = true;
|
||||
if (Bangle.isBTHRMActive()){
|
||||
stopFallback();
|
||||
} else {
|
||||
startFallback();
|
||||
}
|
||||
}
|
||||
inSwitch = false;
|
||||
};
|
||||
|
||||
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];
|
||||
log("Moving app " + app);
|
||||
origSetHRMPower(0, app);
|
||||
Bangle.origSetHRMPower(0, app);
|
||||
Bangle.setBTHRMPower(1, app);
|
||||
if (Bangle._PWR.HRM===undefined) break;
|
||||
}
|
||||
}
|
||||
switchInternalHrm();
|
||||
}
|
||||
|
||||
E.on("kill", ()=>{
|
||||
|
|
|
@ -42,12 +42,20 @@ function draw(y, type, event) {
|
|||
if (event.energy) str += " kJoule: " + event.energy.toFixed(0);
|
||||
g.setFontVector(12).drawString(str,px,y+60);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var firstEventBt = true;
|
||||
var firstEventInt = true;
|
||||
|
||||
|
||||
// This can get called for the boot code to show what's happening
|
||||
function showStatusInfo(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) {
|
||||
if (firstEventBt){
|
||||
clear(24);
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "bthrm",
|
||||
"name": "Bluetooth Heart Rate Monitor",
|
||||
"shortName": "BT HRM",
|
||||
"version": "0.10",
|
||||
"version": "0.12",
|
||||
"description": "Overrides Bangle.js's build in heart rate monitor with an external Bluetooth one.",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -32,8 +32,45 @@
|
|||
Bangle.removeListener('BTHRM', onHRM);
|
||||
if (Bangle.setBTRHMPower) Bangle.setBTHRMPower(0,"recorder");
|
||||
},
|
||||
draw : (x,y) => g.setColor((bpm != "")?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
||||
draw : (x,y) => g.setColor((Bangle.isBTHRMActive && Bangle.isBTHRMActive())?"#00f":"#88f").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
||||
};
|
||||
}
|
||||
};
|
||||
recorders.hrmint = function() {
|
||||
var active = false;
|
||||
var bpmTimeout;
|
||||
var bpm = "", bpmConfidence = "", src="";
|
||||
function onHRM(h) {
|
||||
bpmConfidence = h.confidence;
|
||||
bpm = h.bpm;
|
||||
srv = h.src;
|
||||
if (h.bpm > 0){
|
||||
active = true;
|
||||
print("active" + h.bpm);
|
||||
if (bpmTimeout) clearTimeout(bpmTimeout);
|
||||
bpmTimeout = setTimeout(()=>{
|
||||
print("inactive");
|
||||
active = false;
|
||||
},3000);
|
||||
}
|
||||
}
|
||||
return {
|
||||
name : "HR int",
|
||||
fields : ["Heartrate", "Confidence"],
|
||||
getValues : () => {
|
||||
var r = [bpm,bpmConfidence,src];
|
||||
bpm = ""; bpmConfidence = ""; src="";
|
||||
return r;
|
||||
},
|
||||
start : () => {
|
||||
Bangle.origOn('HRM', onHRM);
|
||||
if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(1,"recorder");
|
||||
},
|
||||
stop : () => {
|
||||
Bangle.removeListener('HRM', onHRM);
|
||||
if (Bangle.origSetHRMPower) Bangle.origSetHRMPower(0,"recorder");
|
||||
},
|
||||
draw : (x,y) => g.setColor(( Bangle.origIsHRMOn && Bangle.origIsHRMOn() && active)?"#0f0":"#8f8").drawImage(atob("DAwBAAAAMMeef+f+f+P8H4DwBgAA"),x,y)
|
||||
};
|
||||
};
|
||||
})
|
||||
|
||||
|
|
|
@ -17,6 +17,14 @@
|
|||
var settings;
|
||||
readSettings();
|
||||
|
||||
function applyCustomSettings(){
|
||||
writeSettings("enabled",true);
|
||||
writeSettings("replace",settings.custom_replace);
|
||||
writeSettings("startWithHrm",settings.custom_startWithHrm);
|
||||
writeSettings("allowFallback",settings.custom_allowFallback);
|
||||
writeSettings("fallbackTimeout",settings.custom_fallbackTimeout);
|
||||
}
|
||||
|
||||
function buildMainMenu(){
|
||||
var mainmenu = {
|
||||
'': { 'title': 'Bluetooth HRM' },
|
||||
|
@ -35,7 +43,6 @@
|
|||
case 1:
|
||||
writeSettings("enabled",true);
|
||||
writeSettings("replace",true);
|
||||
writeSettings("debuglog",false);
|
||||
writeSettings("startWithHrm",true);
|
||||
writeSettings("allowFallback",true);
|
||||
writeSettings("fallbackTimeout",10);
|
||||
|
@ -43,17 +50,11 @@
|
|||
case 2:
|
||||
writeSettings("enabled",true);
|
||||
writeSettings("replace",false);
|
||||
writeSettings("debuglog",false);
|
||||
writeSettings("startWithHrm",false);
|
||||
writeSettings("allowFallback",false);
|
||||
break;
|
||||
case 3:
|
||||
writeSettings("enabled",true);
|
||||
writeSettings("replace",settings.custom_replace);
|
||||
writeSettings("debuglog",settings.custom_debuglog);
|
||||
writeSettings("startWithHrm",settings.custom_startWithHrm);
|
||||
writeSettings("allowFallback",settings.custom_allowFallback);
|
||||
writeSettings("fallbackTimeout",settings.custom_fallbackTimeout);
|
||||
applyCustomSettings();
|
||||
break;
|
||||
}
|
||||
writeSettings("mode",v);
|
||||
|
@ -138,23 +139,23 @@
|
|||
'< Back': function() { E.showMenu(buildMainMenu()); },
|
||||
'Replace HRM': {
|
||||
value: !!settings.custom_replace,
|
||||
format: v => settings.custom_replace ? "On" : "Off",
|
||||
onchange: v => {
|
||||
writeSettings("custom_replace",v);
|
||||
if (settings.mode == 3) applyCustomSettings();
|
||||
}
|
||||
},
|
||||
'Start w. HRM': {
|
||||
value: !!settings.custom_startWithHrm,
|
||||
format: v => settings.custom_startWithHrm ? "On" : "Off",
|
||||
onchange: v => {
|
||||
writeSettings("custom_startWithHrm",v);
|
||||
if (settings.mode == 3) applyCustomSettings();
|
||||
}
|
||||
},
|
||||
'HRM Fallback': {
|
||||
value: !!settings.custom_allowFallback,
|
||||
format: v => settings.custom_allowFallback ? "On" : "Off",
|
||||
onchange: v => {
|
||||
writeSettings("custom_allowFallback",v);
|
||||
if (settings.mode == 3) applyCustomSettings();
|
||||
}
|
||||
},
|
||||
'Fallback Timeout': {
|
||||
|
@ -165,6 +166,7 @@
|
|||
format: v=>v+"s",
|
||||
onchange: v => {
|
||||
writeSettings("custom_fallbackTimout",v*1000);
|
||||
if (settings.mode == 3) applyCustomSettings();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -8,4 +8,6 @@
|
|||
0.08: Select the color of widgets correctly. Additional settings to hide colon.
|
||||
0.09: Larger font size if colon is hidden to improve readability further.
|
||||
0.10: HomeAssistant integration if HomeAssistant is installed.
|
||||
0.11: Performance improvements.
|
||||
0.11: Performance improvements.
|
||||
0.12: Implements a 2D menu.
|
||||
0.13: Clicks < 24px are for widgets, if fullscreen mode is disabled.
|
|
@ -1,18 +1,46 @@
|
|||
# BW Clock
|
||||
A very minimalistic clock with date and time in focus.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
- Fullscreen on/off
|
||||
- Tab left/right of screen to show steps, temperature etc.
|
||||
- Enable / disable lock icon in the settings.
|
||||
- If the "sched" app is installed tab top / bottom of the screen to set the timer.
|
||||
- If HomeAssistant is installed, triggers are shown. Simple select the trigger and touch the middle of the screen to send the trigger to HomeAssistant.
|
||||
- The design is adapted to the theme of your bangle.
|
||||
- The colon (e.g. 7:35 = 735) can be hidden now in the settings.
|
||||
The BW clock provides many features as well as 3rd party integrations:
|
||||
- Bangle data such as steps, heart rate, battery or charging state.
|
||||
- A timer can be set directly. *Requirement: Scheduler library*
|
||||
- Weather temperature as well as the wind speed can be shown. *Requirement: Weather app*
|
||||
- HomeAssistant triggers can be executed directly. *Requirement: HomeAssistant app*
|
||||
|
||||
Note: If some apps are not installed (e.gt. weather app), then this menu item is hidden.
|
||||
|
||||
## Menu
|
||||
2D menu allows you to display lots of different data including data from 3rd party apps and it's also possible to control things e.g. to set a timer or send a HomeAssistant trigger.
|
||||
|
||||
Simply click left / right to go through the menu entries such as Bangle, Timer etc.
|
||||
and click up/down to move into this sub-menu. You can then click in the middle of the screen
|
||||
to e.g. send a trigger via HomeAssistant once you selected it.
|
||||
|
||||
```
|
||||
+5min
|
||||
|
|
||||
Bangle -- Timer[Optional] -- Weather[Optional] -- HomeAssistant [Optional]
|
||||
| | | |
|
||||
Bpm -5min Temperature Trigger1
|
||||
| | |
|
||||
Steps ... ...
|
||||
|
|
||||
Battery
|
||||
```
|
||||
|
||||
## Settings
|
||||
- Fullscreen on/off (widgets are still loaded).
|
||||
- Enable/disable lock icon in the settings. Useful if fullscreen is on.
|
||||
- The colon (e.g. 7:35 = 735) can be hidden in the settings for an even larger time font to improve readability further.
|
||||
- There are no design settings, as your bangle sys settings are used.
|
||||
|
||||
|
||||
## Thanks to
|
||||
<a href="https://www.flaticon.com/free-icons/" title="Icons">Icons created by Flaticon</a>
|
||||
|
||||
|
||||
## Creator
|
||||
- [David Peer](https://github.com/peerdavid)
|
||||
[David Peer](https://github.com/peerdavid)
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
/*
|
||||
/************
|
||||
* Includes
|
||||
*/
|
||||
const locale = require('locale');
|
||||
const storage = require('Storage');
|
||||
|
||||
/*
|
||||
/************
|
||||
* Statics
|
||||
*/
|
||||
const SETTINGS_FILE = "bwclk.setting.json";
|
||||
|
@ -12,14 +12,15 @@ const TIMER_IDX = "bwclk";
|
|||
const W = g.getWidth();
|
||||
const H = g.getHeight();
|
||||
|
||||
/*
|
||||
/************
|
||||
* Settings
|
||||
*/
|
||||
let settings = {
|
||||
fullscreen: false,
|
||||
showLock: true,
|
||||
hideColon: false,
|
||||
showInfo: 0,
|
||||
menuPosX: 0,
|
||||
menuPosY: 0,
|
||||
};
|
||||
|
||||
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||
|
@ -28,22 +29,10 @@ for (const key in saved_settings) {
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
/************
|
||||
* Assets
|
||||
*/
|
||||
|
||||
// Manrope font
|
||||
Graphics.prototype.setLargeFont = function(scale) {
|
||||
// Actual height 48 (49 - 2)
|
||||
this.setFontCustom(
|
||||
E.toString(require('heatshrink').decompress(atob('AFcH+AHFh/gA4sf4AHFn+AA4t/E43+AwsB/gHFgf4PH4AMgJ9Ngf/Pot//6bF/59F///PokfA4J9DEgIABEwYkB/7DDEgIlFCoRMDEgQsEDoRLEEgpoBA4JhGOIsHZ40PdwwA/L4SjHNAgGCP4cHA4wWDA4aVCA4gGDA4SNBe4IiBA4MPHYRBBEwScCA4d/EQUBaoRKDA4UBLQYECgb+EAgMHYYcHa4MPHoLBCBgMfYgcfBgM/PIc/BgN/A4YECIIQEDHwkDHwQHDGwQHENQUHA4d/QIQnCRIJJCSgYTCA4hqCA4hqCA4hiCA4ZCEA4RFBGYbrFAHxDGSohdDcgagFAAjPCEzicDToU/A4jPCAwbQCBwgrBgIHEFYKrDWoa7DaggA/AC0PAYV+AYSBCgKpCg4DDVIUfAYZ9BToIDDPoKVBAYfARoQDDXgMPFwTIBdYSYCv4LCv7zCXgYKCXAK8CHoUPXgY9Cn/vEYMPEwX/z46Bj4mBgf+n77CDwX4v54EIIIzCOgX/4I+CAQI9BHYQCCQ4I7CRASDBHYQHCv/Aj4+BGYIeBGAI+Bj/8AIIRBQIZjCRIiWBXgYHCPQgHBBgJ6DA4IEBPQaKBGYQ+BbgiCCAGZFDIIUBaAZBCgYHCQAQTBA4SACUwS8DDYQHBQAbVCQAYwBA4SABgYEBPoQCBFgU/CQWACgRDCHwKVCIYX+aYRDCHwMPAgY+Cn4EDHwX/AgY+B8bEFj/HA4RGCn+f94MBv45Cv+fA4J6C//+j5gBGIMBFoJWBQoRMB8E//4DBHIJcBv4HBEwJUCA4ImCj5MBA4KZCPYQHBZgRBCE4LICvwaCXAYA5PgQAEMIQAEUwQADQAJlCAARlBWYIACT4JtDAAMPA4IWESgg8CAwI+EEoPhHwYlCgY+DEoP4g4+DEoPAh4+CEoReBHwUfLYU/CwgMBXARqBHYQCCGoIjBgI+CgZSCHwcHAYY+Ch4lBJ4IbCjhACPwqUBPwqFCPwhQBIQZ+DOAKVFXooHCXop9DFAi8EFAT0GPoYAygwFEgOATISLDwBWDTQc/A4L6CTQKkCVQX+BYIHBDwX+BYIHBVQX8B4KqD+/wA4aBBj/AgK8CQIIJBA4a/BBIMBAgL/BAgUDYgL/BAII7BAQXgAII7BAQXAYQQxBYARrCMwQ0BAgV/HwYECHwgEBgY+EA4MPGwI8BA4UfGwI8BgYHBPofAQYOHPoeAR4QmBHwQHCEwI+CA4RVBHwQHCaggnBDwQHEHoIAEEQIA6v5NFfgSECBwZtEf4IHFOYQHEj4HGDwYHCDwPgv/jA4UHXQS8E/ED/AHDZ4MPSYKlCv+AYwIHDDwL7EgL7DAgTzCEwIpCeYTZBg4CBeYIJBAgICBFgIJBAgICBeYIEDHII0BAgg+EgI5CMocHGwJBCA4MfGwMD/h/BwF/PoQHC451CJIMDSgIjBA4PAA4QmBA4IhBA4JVBgEMA4bUDV4QeCAAf/HoIAENIIApOoIAEW4QAEW4QAEW4QAEWQRSFNIcDfYQMDny8DO4Q7BAQQjCewh+EHwcPToQ+Dv//ewkHUoI+En68DeIS0EHwMf/46CeYYlCHwQ0BKIY+BGgJ4Dh/nGgZZCAwKPEHYLpFDoKuFGgj4JgY0EHwQ0EYhIA6MAkf+BRBLIa5BQAJSCBgP4R4iVB/YHERoIACA4QGDE4SFBAoV/A4MH/ggBWIL7C8EfVoL4DwBHBFYIHBfYIRBAgT7CDgQEBgP4BgUBEIMDDgIMBgYMBg/gBgS5Ch/ABgUPFIMf4EHA4IEBHwUPCgJGCIIM/CgLgCAQJlBFIQFB44HBEIUBQYc/EIIHDAAIuBA4oeBRoSfBLAIHC/gHBEwIXC+AHBZghHBDwQADj4WCAHEPAwpWBKYYOCLwIHELYJUBghlDA4UcQogHBvgeDD4K0DDwIHBWgQeB4CyBh68CUAMf8DeCdIYHDdIfAfYjxCAgj2BAgbHCvwJCIIYCBBIMDHIX4BgUHFwMD+AMCA4Q0BAgg5CHwxICAQY5BdgQHBEgMDIYV/DgR1CA4PwP4KvDRgIACEYIHFWggABMQQHEZwd/Dwq1DHoTFEdooA/ACrBBcAZmC8DTCAATGBaYR+DwDTCRwbYDAASLBCIIGCFgQRBAG4='))),
|
||||
46,
|
||||
atob("EhooGyUkJiUnISYnFQ=="),
|
||||
63+(scale<<8)+(1<<16)
|
||||
);
|
||||
return this;
|
||||
};
|
||||
|
||||
Graphics.prototype.setXLargeFont = function(scale) {
|
||||
// Actual height 53 (55 - 3)
|
||||
this.setFontCustom(
|
||||
|
@ -54,12 +43,26 @@ Graphics.prototype.setXLargeFont = function(scale) {
|
|||
);
|
||||
};
|
||||
|
||||
|
||||
Graphics.prototype.setLargeFont = function(scale) {
|
||||
// Actual height 47 (48 - 2)
|
||||
this.setFontCustom(
|
||||
atob('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAD/AAAAAAAAA/wAAAAAAAAP8AAAAAAAAD/AAAAAAAAA/wAAAAAAAAP8AAAAAAAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA8AAAAAAAAD/AAAAAAAAP/wAAAAAAAf/8AAAAAAB///AAAAAAH///wAAAAAf///8AAAAB/////AAAAH////8AAAAP////wAAAA/////AAAAB////+AAAAA////4AAAAAP///gAAAAAD//+AAAAAAA//4AAAAAAAP/gAAAAAAAD/AAAAAAAAA8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///+AAAAAB////8AAAAB/////wAAAA/////+AAAA//////wAAAf/////+AAAH//////wAAD//////+AAB/+AAAf/gAAf+AAAA/8AAH/AAAAH/AAD/gAAAA/4AA/wAAAAH+AAP8AAAAB/gAD+AAAAAf4AA/gAAAAH+AAP4AAAAA/gAD+AAAAAf4AA/wAAAAH+AAP8AAAAB/gAD/AAAAA/4AA/4AAAAP+AAH/AAAAH/AAB/4AAAH/wAAP/wAAP/4AAD//////+AAAf//////AAAD//////gAAAf/////wAAAD/////4AAAAf////4AAAAB////4AAAAAB///gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAAAAAAAH/AAAAAAAAD/gAAAAAAAA/4AAAAAAAAf8AAAAAAAAH+AAAAAAAAD/gAAAAAAAB/wAAAAAAAAf8AAAAAAAAP///////AAD///////wAA///////8AAP///////AAD///////wAA///////8AAP///////AAD///////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAB/AAAA/gAAA/wAAA/4AAAf8AAAf+AAAP/AAAP/gAAH/wAAH/4AAD/8AAD/+AAB//AAA//gAA//wAAf/AAAP/8AAH/AAAH//AAD/gAAD//wAA/wAAB//8AAP8AAA///AAD/AAAf+fwAA/gAAP/n8AAP4AAH/x/AAD+AAD/4fwAA/gAB/8H8AAP8AAf+B/AAD/AAP/AfwAA/4AH/gH8AAH/AH/wB/AAB/8H/4AfwAAP///8AH8AAD////AB/AAAf///gAfwAAD///wAH8AAAf//4AB/AAAD//4AAfwAAAP/8AAH8AAAAf4AAB/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAADgAAAfwAAAB+AAAH8AAAAfwAAB/AAAAH+AAAfwAAAB/wAAH8AAAA/+AAB/AAAAP/gAAfwA4AA/8AAH8AfgAH/AAB/AP8AA/4AAfwD/gAH+AAH8B/4AB/gAB/A/8AAf4AAfwf/AAD+AAH8P/wAA/gAB/H/8AAf4AAfz//gAH+AAH8//4AB/gAB/f//AA/4AAf/+/4Af8AAH//P/AP/AAB//j////gAAf/wf///4AAH/4H///8AAB/8A///+AAAf+AH///AAAH/AA///gAAB/gAD//wAAAfwAAP/wAAAAAAAAOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAAAAAAAH/wAAAAAAAH/8AAAAAAAH//AAAAAAAH//wAAAAAAH//8AAAAAAH///AAAAAAH///wAAAAAH///8AAAAAP//9/AAAAAP//8fwAAAAP//4H8AAAAP//4B/AAAAP//4AfwAAAP//4AH8AAAD//4AB/AAAA//4AAfwAAAP/4AAH8AAAD/wAAB/AAAA/wAAAfwAAAPwAH////AADwAB////wAAwAAf///8AAAAAH////AAAAAB////wAAAAAf///8AAAAAH////AAAAAA////wAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAAAAAAfwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAcAAAAAAAGAHwAAAB///gB+AAAH///8AfwAAB////AP+AAAf///wD/wAAH///+A/+AAB////gP/gAAf///4A/8AAH/8P8AH/AAB/AD+AA/4AAfwA/gAH+AAH8AfwAB/gAB/AH8AAf4AAfwB/AAH+AAH8AfwAB/gAB/AH8AAf4AAfwB/gAH+AAH8Af4AB/gAB/AH/AA/wAAfwB/4Af8AAH8AP/AP/AAB/AD////gAAfwAf///wAAH8AD///8AAB/AA///+AAAfwAH///AAAAAAA///gAAAAAAD//gAAAAAAAP/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB///4AAAAAH////wAAAAH/////AAAAD/////4AAAB//////AAAA//////4AAAf//////AAAP//////4AAD/8D/w/+AAB/4B/wD/wAAf8A/wAf8AAP+AP4AD/gAD/AD+AAf4AA/wB/AAH+AAP4AfwAB/gAD+AH8AAf4AA/gB/AAH+AAP4AfwAB/gAD+AH+AAf4AA/wB/gAH+AAP8Af8AD/gAD/gH/gB/wAAf8A/8A/8AAH/AP///+AAB/gB////gAAPwAP///wAAB4AD///4AAAMAAf//8AAAAAAD//+AAAAAAAP/+AAAAAAAA/+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAAAAAAfwAAAAAAAAH8AAAAAAAAB/AAAAABwAAfwAAAAB8AAH8AAAAD/AAB/AAAAD/wAAfwAAAH/8AAH8AAAH//AAB/AAAP//wAAfwAAP//8AAH8AAf//+AAB/AAf//8AAAfwA///8AAAH8A///4AAAB/A///4AAAAfx///wAAAAH9///wAAAAB////gAAAAAf///gAAAAAH///AAAAAAB///AAAAAAAf/+AAAAAAAH/+AAAAAAAB/8AAAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAAAAAAAAf/AAAAAP+Af/8AAAAP/4P//wAAAP//P//+AAAH//////wAAB//////8AAA///////gAAf//////8AAH////gP/AAD/wf/wA/wAA/4D/4AP+AAP8Af8AB/gAD/AH/AAf4AA/gA/wAH+AAP4AP4AA/gAD+AD/AAP4AA/gA/wAH+AAP8Af8AB/gAD/AH/AAf4AA/4D/4AP+AAP/B//AH/AAB////4D/wAAf//////8AAD//////+AAAf//////AAAH//////wAAA//8///4AAAD/+D//8AAAAP+Af/8AAAAAAAB/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB/gAAAAAAAB//AAAAAAAB//8AAAAAAB///gAAgAAA///8AAcAAAf///gAPAAAH///8AH4AAD////AD/AAB/+H/4B/wAAf+Af+Af8AAP+AB/wD/gAD/gAf8Af4AA/wAD/AH+AAP8AA/wB/gAD+AAH8AP4AA/gAB/AD+AAP4AAfwB/gAD+AAH8Af4AA/wAD/AH+AAP8AA/gD/gAD/gAf4A/wAAf8AP8A/8AAH/gH/Af/AAA///////gAAP//////wAAB//////8AAAP/////+AAAB//////AAAAP/////AAAAA/////gAAAAD////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/wA/wAAAAAP8AP8AAAAAD/AD/AAAAAA/wA/wAAAAAP8AP8AAAAAD/AD/AAAAAA/wA/wAAAAAP8AP8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='),
|
||||
46,
|
||||
atob("ExspGyUkJiQnISYnFQ=="),
|
||||
62+(scale<<8)+(1<<16)
|
||||
);
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
Graphics.prototype.setMediumFont = function(scale) {
|
||||
// Actual height 41 (42 - 2)
|
||||
this.setFontCustom(atob("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAAAAAAB/AAAAAAAP/AAAAAAD//AAAAAA///AAAAAP///AAAAB///8AAAAf///AAAAH///wAAAB///+AAAAH///gAAAAH//4AAAAAH/+AAAAAAH/wAAAAAAH8AAAAAAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAH////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gAAH+AAD+AAAD/AAH8AAAB/AAH4AAAA/gAH4AAAAfgAH4AAAAfgAPwAAAAfgAPwAAAAfgAPwAAAAfgAHwAAAAfgAH4AAAAfgAH4AAAA/gAH8AAAA/AAD+AAAD/AAD/gAAH/AAB/////+AAB/////8AAA/////4AAAf////wAAAH////gAAAB///+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPgAAAAAAAfwAAAAAAA/gAAAAAAA/AAAAAAAB/AAAAAAAD+AAAAAAAD8AAAAAAAH8AAAAAAAH//////AAH//////AAH//////AAH//////AAH//////AAH//////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD4AAA/AAAP4AAB/AAAf4AAD/AAA/4AAD/AAB/4AAH/AAD/4AAP/AAH/AAAf/AAH8AAA//AAH4AAB//AAP4AAD//AAPwAAH+/AAPwAAP8/AAPwAAf4/AAPwAA/4/AAPwAA/w/AAPwAB/g/AAPwAD/A/AAP4AH+A/AAH8AP8A/AAH/A/4A/AAD///wA/AAD///gA/AAB///AA/AAA//+AA/AAAP/8AA/AAAD/wAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADgAAH4AAAHwAAH4AAAH4AAH4AAAH8AAH4AAAP+AAH4AAAH+AAH4A4AB/AAH4A+AA/AAH4B/AA/gAH4D/AAfgAH4H+AAfgAH4P+AAfgAH4f+AAfgAH4/+AAfgAH5/+AAfgAH5//AAfgAH7+/AA/gAH/8/gB/AAH/4f4H/AAH/wf//+AAH/gP//8AAH/AH//8AAH+AD//wAAH8AB//gAAD4AAf+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+AAAAAAAD/AAAAAAAP/AAAAAAB//AAAAAAH//AAAAAAf//AAAAAB///AAAAAH///AAAAAf/8/AAAAB//w/AAAAH/+A/AAAA//4A/AAAD//gA/AAAH/+AA/AAAH/4AA/AAAH/gAA/AAAH+AAA/AAAHwAAA/AAAHAAf///AAEAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAf///AAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAA/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAAP/AHgAAH///AP4AAH///gP8AAH///gP8AAH///gP+AAH///gD/AAH/A/AB/AAH4A/AA/gAH4A+AAfgAH4B+AAfgAH4B+AAfgAH4B8AAfgAH4B8AAfgAH4B+AAfgAH4B+AAfgAH4B+AA/gAH4B/AA/AAH4A/gD/AAH4A/4H+AAH4Af//+AAH4AP//8AAH4AP//4AAHwAD//wAAAAAB//AAAAAAAf8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA///8AAAAD////AAAAP////wAAAf////4AAA/////8AAB/////+AAD/gP4H+AAD/AfgD/AAH8A/AB/AAH8A/AA/gAH4B+AAfgAH4B+AAfgAPwB8AAfgAPwB8AAfgAPwB+AAfgAPwB+AAfgAH4B+AAfgAH4B/AA/gAH8B/AB/AAH+A/wD/AAD+A/8P+AAB8Af//+AAB4AP//8AAAwAH//4AAAAAD//gAAAAAA//AAAAAAAP4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAAAAPwAAAAHAAPwAAAA/AAPwAAAD/AAPwAAAf/AAPwAAB//AAPwAAP//AAPwAA//8AAPwAH//wAAPwAf/+AAAPwB//4AAAPwP//AAAAPw//8AAAAP3//gAAAAP//+AAAAAP//wAAAAAP//AAAAAAP/4AAAAAAP/gAAAAAAP+AAAAAAAHwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP+AAAAH+A//gAAAf/h//4AAA//z//8AAB/////+AAD/////+AAD///+H/AAH+H/4B/AAH8B/wA/gAH4A/gAfgAH4A/gAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAPwA/AAfgAH4A/gAfgAH4A/gAfgAH8B/wA/gAH/H/4B/AAD///+H/AAD/////+AAB/////+AAA//z//8AAAf/h//4AAAH+A//gAAAAAAH+AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/gAAAAAAD/8AAAAAAP/+AAAAAAf//AAcAAA///gA8AAB///wB+AAD/x/4B/AAD+AP4B/AAH8AH8A/gAH4AH8A/gAH4AD8AfgAP4AD8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAPwAB8AfgAH4AD8AfgAH4AD4A/gAH8AH4B/AAD+APwD/AAD/g/wP+AAB/////+AAA/////8AAAf////4AAAP////wAAAH////AAAAA///8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAD8APwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=="), 46, atob("DxcjFyAfISAiHCAiEg=="), 54+(scale<<8)+(1<<16));
|
||||
return this;
|
||||
};
|
||||
|
||||
|
||||
Graphics.prototype.setSmallFont = function(scale) {
|
||||
// Actual height 28 (27 - 0)
|
||||
this.setFontCustom(
|
||||
|
@ -71,6 +74,7 @@ Graphics.prototype.setSmallFont = function(scale) {
|
|||
return this;
|
||||
};
|
||||
|
||||
|
||||
function imgLock(){
|
||||
return {
|
||||
width : 16, height : 16, bpp : 1,
|
||||
|
@ -95,6 +99,14 @@ function imgBattery(){
|
|||
}
|
||||
}
|
||||
|
||||
function imgCharging() {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("//+v///k///4AQPwBANgBoMxBoMb/P+h/w/kH8H4gfB+EBwfggHH4EAt4CBn4CBj4CBh4FCCIO/8EB//Agf/wEH/8Gh//x////fAQIA="))
|
||||
}
|
||||
}
|
||||
|
||||
function imgBpm() {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
|
@ -111,6 +123,14 @@ function imgTemperature() {
|
|||
}
|
||||
}
|
||||
|
||||
function imgWeather(){
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AAcYAQ0MgEwAQUAngLB/8AgP/wACCgf/4Fz//OAQQICCIoaCEAQpGHA4ACA="))
|
||||
}
|
||||
}
|
||||
|
||||
function imgWind () {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
|
@ -127,14 +147,6 @@ function imgTimer() {
|
|||
}
|
||||
}
|
||||
|
||||
function imgCharging() {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
transparent : 1,
|
||||
buffer : require("heatshrink").decompress(atob("//+v///k///4AQPwBANgBoMxBoMb/P+h/w/kH8H4gfB+EBwfggHH4EAt4CBn4CBj4CBh4FCCIO/8EB//Agf/wEH/8Gh//x////fAQIA="))
|
||||
}
|
||||
}
|
||||
|
||||
function imgWatch() {
|
||||
return {
|
||||
width : 24, height : 24, bpp : 1,
|
||||
|
@ -143,56 +155,93 @@ function imgWatch() {
|
|||
}
|
||||
}
|
||||
|
||||
function imgHomeAssistant() {
|
||||
return {
|
||||
width : 48, height : 48, bpp : 1,
|
||||
transparent : 0,
|
||||
buffer : require("heatshrink").decompress(atob("AD8BwAFDg/gAocP+AFDj4FEn/8Aod//wFD/1+FAf4j+8AoMD+EPDAUH+OPAoUP+fPAoUfBYk/C4l/EYIwC//8n//FwIFEgYFD4EH+E8nkP8BdBAonjjk44/wj/nzk58/4gAFDF4PgCIMHAoPwhkwh4FB/EEkEfIIWAHwIFC4A+BAoXgg4FDL4IFDL4IFDLIYFkAEQA=="))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* INFO ENTRIES
|
||||
* List of [Data, Icon, left/right, Function to execute]
|
||||
|
||||
/************
|
||||
* 2D MENU with entries of:
|
||||
* [name, icon, opt[customDownFun], opt[customUpFun], opt[customCenterFun]]
|
||||
*
|
||||
*/
|
||||
var infoArray = [
|
||||
function(){ return [ null, null, "left", null ] },
|
||||
function(){ return [ "Bangle", imgWatch(), "right", null ] },
|
||||
function(){ return [ E.getBattery() + "%", imgBattery(), "left", null ] },
|
||||
function(){ return [ getSteps(), imgSteps(), "left", null ] },
|
||||
function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm(), "left", null] },
|
||||
function(){ return [ getWeather().temp, imgTemperature(), "left", null ] },
|
||||
function(){ return [ getWeather().wind, imgWind(), "left", null ] },
|
||||
];
|
||||
var menu = [
|
||||
[
|
||||
function(){ return [ null, null ] },
|
||||
],
|
||||
[
|
||||
function(){ return [ "Bangle", imgWatch() ] },
|
||||
function(){ return [ E.getBattery() + "%", Bangle.isCharging() ? imgCharging() : imgBattery() ] },
|
||||
function(){ return [ getSteps(), imgSteps() ] },
|
||||
function(){ return [ Math.round(Bangle.getHealthStatus("last").bpm) + " bpm", imgBpm()] },
|
||||
]
|
||||
]
|
||||
|
||||
/*
|
||||
* We append the HomeAssistant integrations if HomeAssistant is available
|
||||
* Timer Menu
|
||||
*/
|
||||
try{
|
||||
require('sched');
|
||||
menu.push([
|
||||
function(){
|
||||
var text = isAlarmEnabled() ? getAlarmMinutes() + " min." : "Timer";
|
||||
return [text, imgTimer(), () => decreaseAlarm(), () => increaseAlarm(), null ]
|
||||
},
|
||||
]);
|
||||
} catch(ex) {
|
||||
// If sched is not installed, we hide this menu item
|
||||
}
|
||||
|
||||
/*
|
||||
* WEATHER MENU
|
||||
*/
|
||||
if(storage.readJSON('weather.json') !== undefined){
|
||||
menu.push([
|
||||
function(){ return [ "Weather", imgWeather() ] },
|
||||
function(){ return [ getWeather().temp, imgTemperature() ] },
|
||||
function(){ return [ getWeather().wind, imgWind() ] },
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* HOME ASSISTANT MENU
|
||||
*/
|
||||
try{
|
||||
var triggers = require("ha.lib.js").getTriggers();
|
||||
var haMenu = [
|
||||
function(){ return [ "Home", imgHomeAssistant() ] },
|
||||
];
|
||||
|
||||
triggers.forEach(trigger => {
|
||||
infoArray.push(function(){
|
||||
return [trigger.display, trigger.getIcon(), "left", function(){
|
||||
haMenu.push(function(){
|
||||
return [trigger.display, trigger.getIcon(), () => {}, () => {}, function(){
|
||||
var ha = require("ha.lib.js");
|
||||
ha.sendTrigger("TRIGGER_BW");
|
||||
ha.sendTrigger(trigger.trigger);
|
||||
}]
|
||||
});
|
||||
})
|
||||
menu.push(haMenu);
|
||||
} catch(ex){
|
||||
// Nothing to do if HomeAssistant is not available...
|
||||
}
|
||||
const NUM_INFO=infoArray.length;
|
||||
|
||||
|
||||
function getInfoEntry(){
|
||||
if(isAlarmEnabled()){
|
||||
return [getAlarmMinutes() + " min.", imgTimer(), "left", null]
|
||||
} else if(Bangle.isCharging()){
|
||||
return [E.getBattery() + "%", imgCharging(), "left", null]
|
||||
} else{
|
||||
// In case the user removes HomeAssistant entries, showInfo
|
||||
// could be larger than infoArray.length...
|
||||
settings.showInfo = settings.showInfo % infoArray.length;
|
||||
return infoArray[settings.showInfo]();
|
||||
}
|
||||
// If HomeAssistant is not installed, we hide this item
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
function getMenuEntry(){
|
||||
// In case the user removes HomeAssistant entries, showInfo
|
||||
// could be larger than infoArray.length...
|
||||
settings.menuPosX = settings.menuPosX % menu.length;
|
||||
settings.menuPosY = settings.menuPosY % menu[settings.menuPosX].length;
|
||||
return menu[settings.menuPosX][settings.menuPosY]();
|
||||
}
|
||||
|
||||
|
||||
/************
|
||||
* Helper
|
||||
*/
|
||||
function getSteps() {
|
||||
|
@ -229,7 +278,7 @@ function getWeather(){
|
|||
|
||||
// Wind
|
||||
const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
|
||||
weather.wind = Math.round(wind[1]) + " km/h";
|
||||
weather.wind = Math.round(wind[1]) + "kph";
|
||||
|
||||
return weather
|
||||
|
||||
|
@ -238,15 +287,16 @@ function getWeather(){
|
|||
}
|
||||
|
||||
return {
|
||||
temp: "? °C",
|
||||
hum: "-",
|
||||
txt: "-",
|
||||
wind: "? km/h",
|
||||
wdir: "-",
|
||||
wrose: "-"
|
||||
temp: " ? ",
|
||||
hum: " ? ",
|
||||
txt: " ? ",
|
||||
wind: " ? ",
|
||||
wdir: " ? ",
|
||||
wrose: " ? "
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function isAlarmEnabled(){
|
||||
try{
|
||||
var alarm = require('sched');
|
||||
|
@ -261,6 +311,7 @@ function isAlarmEnabled(){
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
function getAlarmMinutes(){
|
||||
if(!isAlarmEnabled()){
|
||||
return -1;
|
||||
|
@ -271,6 +322,7 @@ function getAlarmMinutes(){
|
|||
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
|
||||
}
|
||||
|
||||
|
||||
function increaseAlarm(){
|
||||
try{
|
||||
var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
|
||||
|
@ -282,6 +334,7 @@ function increaseAlarm(){
|
|||
} catch(ex){ }
|
||||
}
|
||||
|
||||
|
||||
function decreaseAlarm(){
|
||||
try{
|
||||
var minutes = getAlarmMinutes();
|
||||
|
@ -301,10 +354,9 @@ function decreaseAlarm(){
|
|||
}
|
||||
|
||||
|
||||
/*
|
||||
* DRAW functions
|
||||
/************
|
||||
* DRAW
|
||||
*/
|
||||
|
||||
function draw() {
|
||||
// Queue draw again
|
||||
queueDraw();
|
||||
|
@ -323,8 +375,8 @@ function drawDate(){
|
|||
g.reset().clearRect(0,0,W,W);
|
||||
|
||||
// Draw date
|
||||
y = parseInt(y/2);
|
||||
y += settings.fullscreen ? 2 : 15;
|
||||
y = parseInt(y/2)+4;
|
||||
y += settings.fullscreen ? 0 : 13;
|
||||
var date = new Date();
|
||||
var dateStr = date.getDate();
|
||||
dateStr = ("0" + dateStr).substr(-2);
|
||||
|
@ -338,13 +390,12 @@ function drawDate(){
|
|||
var fullDateW = dateW + 10 + dayW;
|
||||
|
||||
g.setFontAlign(-1,0);
|
||||
g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-12);
|
||||
g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+11);
|
||||
|
||||
g.setMediumFont();
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawString(dateStr, W/2 - fullDateW / 2, y+1);
|
||||
|
||||
g.setSmallFont();
|
||||
g.drawString(dayStr, W/2 - fullDateW/2 + 10 + dateW, y-12);
|
||||
g.drawString(monthStr, W/2 - fullDateW/2 + 10 + dateW, y+11);
|
||||
}
|
||||
|
||||
|
||||
|
@ -368,13 +419,13 @@ function drawTime(){
|
|||
// Set y coordinates correctly
|
||||
y += parseInt((H - y)/2) + 5;
|
||||
|
||||
var infoEntry = getInfoEntry();
|
||||
var infoStr = infoEntry[0];
|
||||
var infoImg = infoEntry[1];
|
||||
var printImgLeft = infoEntry[2] == "left";
|
||||
var menuEntry = getMenuEntry();
|
||||
var menuName = menuEntry[0];
|
||||
var menuImg = menuEntry[1];
|
||||
var printImgLeft = settings.menuPosY != 0;
|
||||
|
||||
// Show large or small time depending on info entry
|
||||
if(infoStr == null){
|
||||
if(menuName == null){
|
||||
if(settings.hideColon){
|
||||
g.setXLargeFont();
|
||||
} else {
|
||||
|
@ -386,8 +437,8 @@ function drawTime(){
|
|||
}
|
||||
g.drawString(timeStr, W/2, y);
|
||||
|
||||
// Draw info if set
|
||||
if(infoStr == null){
|
||||
// Draw menu if set
|
||||
if(menuName == null){
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -395,18 +446,18 @@ function drawTime(){
|
|||
g.setFontAlign(0,0);
|
||||
g.setSmallFont();
|
||||
var imgWidth = 0;
|
||||
if(infoImg !== undefined){
|
||||
imgWidth = 26.0;
|
||||
var strWidth = g.stringWidth(infoStr);
|
||||
var scale = imgWidth / infoImg.width;
|
||||
if(menuImg !== undefined){
|
||||
imgWidth = 24.0;
|
||||
var strWidth = g.stringWidth(menuName);
|
||||
var scale = imgWidth / menuImg.width;
|
||||
g.drawImage(
|
||||
infoImg,
|
||||
menuImg,
|
||||
W/2 + (printImgLeft ? -strWidth/2-2 : strWidth/2+2) - parseInt(imgWidth/2),
|
||||
y - parseInt(imgWidth/2),
|
||||
{ scale: scale }
|
||||
);
|
||||
}
|
||||
g.drawString(infoStr, printImgLeft ? W/2 + imgWidth/2 + 2 : W/2 - imgWidth/2 - 2, y+3);
|
||||
g.drawString(menuName, printImgLeft ? W/2 + imgWidth/2 + 2 : W/2 - imgWidth/2 - 2, y+3);
|
||||
}
|
||||
|
||||
|
||||
|
@ -463,53 +514,78 @@ Bangle.on('lock', function(isLocked) {
|
|||
Bangle.on('charging',function(charging) {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
|
||||
// Jump to battery
|
||||
settings.menuPosX = 1;
|
||||
settings.menuPosY = 1;
|
||||
draw();
|
||||
});
|
||||
|
||||
Bangle.on('touch', function(btn, e){
|
||||
var left = parseInt(g.getWidth() * 0.3);
|
||||
var widget_size = settings.fullscreen ? 0 : 20; // Its not exactly 24px -- empirically it seems that 20 worked better...
|
||||
var left = parseInt(g.getWidth() * 0.22);
|
||||
var right = g.getWidth() - left;
|
||||
var upper = parseInt(g.getHeight() * 0.3);
|
||||
var upper = parseInt(g.getHeight() * 0.22) + widget_size;
|
||||
var lower = g.getHeight() - upper;
|
||||
|
||||
var is_left = e.x < left;
|
||||
var is_right = e.x > right;
|
||||
var is_upper = e.y < upper;
|
||||
var is_lower = e.y > lower;
|
||||
var is_left = e.x < left && !is_upper && !is_lower;
|
||||
var is_right = e.x > right && !is_upper && !is_lower;
|
||||
var is_center = !is_upper && !is_lower && !is_left && !is_right;
|
||||
|
||||
if(is_upper){
|
||||
Bangle.buzz(40, 0.6);
|
||||
increaseAlarm();
|
||||
drawTime();
|
||||
}
|
||||
|
||||
if(is_lower){
|
||||
Bangle.buzz(40, 0.6);
|
||||
decreaseAlarm();
|
||||
settings.menuPosY = (settings.menuPosY+1) % menu[settings.menuPosX].length;
|
||||
|
||||
// Handle custom menu entry function
|
||||
var menuEntry = getMenuEntry();
|
||||
if(menuEntry.length > 2){
|
||||
menuEntry[2]();
|
||||
}
|
||||
|
||||
drawTime();
|
||||
}
|
||||
|
||||
if(is_upper){
|
||||
if(e.y < widget_size){
|
||||
return;
|
||||
}
|
||||
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.menuPosY = settings.menuPosY-1;
|
||||
settings.menuPosY = settings.menuPosY < 0 ? menu[settings.menuPosX].length-1 : settings.menuPosY;
|
||||
|
||||
// Handle custom menu entry function
|
||||
var menuEntry = getMenuEntry();
|
||||
if(menuEntry.length > 3){
|
||||
menuEntry[3]();
|
||||
}
|
||||
|
||||
drawTime();
|
||||
}
|
||||
|
||||
if(is_right){
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.showInfo = (settings.showInfo+1) % NUM_INFO;
|
||||
settings.menuPosX = (settings.menuPosX+1) % menu.length;
|
||||
settings.menuPosY = 0;
|
||||
drawTime();
|
||||
}
|
||||
|
||||
if(is_left){
|
||||
Bangle.buzz(40, 0.6);
|
||||
settings.showInfo = settings.showInfo-1;
|
||||
settings.showInfo = settings.showInfo < 0 ? NUM_INFO-1 : settings.showInfo;
|
||||
settings.menuPosY = 0;
|
||||
settings.menuPosX = settings.menuPosX-1;
|
||||
settings.menuPosX = settings.menuPosX < 0 ? menu.length-1 : settings.menuPosX;
|
||||
drawTime();
|
||||
}
|
||||
|
||||
if(is_center){
|
||||
var infoEntry = getInfoEntry();
|
||||
var fun = infoEntry[3];
|
||||
if(fun != null){
|
||||
var menuEntry = getMenuEntry();
|
||||
if(menuEntry.length > 4){
|
||||
Bangle.buzz(80, 0.6).then(()=>{
|
||||
try{
|
||||
fun();
|
||||
menuEntry[4]();
|
||||
setTimeout(()=>{
|
||||
Bangle.buzz(80, 0.6);
|
||||
}, 250);
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"id": "bwclk",
|
||||
"name": "BW Clock",
|
||||
"version": "0.11",
|
||||
"description": "BW Clock.",
|
||||
"version": "0.13",
|
||||
"description": "A very minimalistic clock with date and time in focus.",
|
||||
"readme": "README.md",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}, {"url":"screenshot_2.png"}, {"url":"screenshot_3.png"}, {"url":"screenshot_4.png"}],
|
||||
|
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 3.0 KiB After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.2 KiB |
|
@ -0,0 +1,2 @@
|
|||
...
|
||||
0.03: First update with ChangeLog Added
|
|
@ -0,0 +1,2 @@
|
|||
...
|
||||
0.02: First update with ChangeLog Added
|
|
@ -1,3 +1,4 @@
|
|||
0.01: Core functionnality
|
||||
0.02: Offer to enable HID if disabled
|
||||
0.03: Adds Readme and tags to be used by App Loader
|
||||
0.04: Adds Bangle.js 2 support, Buzz and Touch
|
||||
|
|
|
@ -7,7 +7,7 @@ Control the camera shutter from your phone using your watch
|
|||
1. In settings, enable HID for "Keyboard & Media".
|
||||
2. Pair your watch to your phone.
|
||||
3. Load your camera app on your phone.
|
||||
4. There you go, launch the app on your watch and press button 2 to trigger the shutter !
|
||||
4. There you go, launch the app on your watch and press the button (button 2 on Bangle.js 1) to trigger the shutter !
|
||||
|
||||
## How does it work ?
|
||||
|
||||
|
|
|
@ -1,25 +1,29 @@
|
|||
var storage = require('Storage');
|
||||
|
||||
const settings = storage.readJSON('setting.json',1) || { HID: false };
|
||||
|
||||
const isB2 = process.env.HWVERSION === 2;
|
||||
var sendHid, camShot, profile;
|
||||
|
||||
if (settings.HID=="kbmedia") {
|
||||
profile = 'camShutter';
|
||||
sendHid = function (code, cb) {
|
||||
try {
|
||||
NRF.sendHIDReport([1,code], () => {
|
||||
NRF.sendHIDReport([1,0], () => {
|
||||
if (cb) cb();
|
||||
});
|
||||
});
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
try {
|
||||
NRF.sendHIDReport([1,code], () => {
|
||||
NRF.sendHIDReport([1,0], () => {
|
||||
if (cb) cb();
|
||||
});
|
||||
});
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
};
|
||||
camShot = function (cb) { sendHid(0x80, cb); };
|
||||
} else {
|
||||
|
||||
E.showPrompt("Enable HID?",{title:"HID disabled"}).then(function(enable) {
|
||||
|
||||
if (enable) {
|
||||
settings.HID = "kbmedia";
|
||||
require("Storage").write('setting.json', settings);
|
||||
|
@ -31,10 +35,15 @@ function drawApp() {
|
|||
g.clear();
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
g.fillCircle(122,127,60);
|
||||
g.drawImage(storage.read("hidcam.img"),100,105);
|
||||
const d = g.getWidth() - 18;
|
||||
|
||||
if (!isB2) { // Bangle.js 1
|
||||
g.fillCircle(122,127,60);
|
||||
g.drawImage(storage.read("hidcam.img"),100,105);
|
||||
const d = g.getWidth() - 18;
|
||||
} else {
|
||||
g.fillCircle(90,95,60);
|
||||
g.drawImage(storage.read("hidcam.img"),65,70);
|
||||
const d = g.getWidth() - 18;
|
||||
}
|
||||
function c(a) {
|
||||
return {
|
||||
width: 8,
|
||||
|
@ -46,12 +55,27 @@ function drawApp() {
|
|||
g.fillRect(180,130, 240, 124);
|
||||
}
|
||||
|
||||
if (camShot) {
|
||||
setWatch(function(e) {
|
||||
E.showMessage('camShot !');
|
||||
setTimeout(drawApp, 1000);
|
||||
camShot(() => {});
|
||||
}, BTN2, { edge:"falling",repeat:true,debounce:50});
|
||||
|
||||
if (camShot) {
|
||||
if (!isB2) { // Bangle.js 1
|
||||
setWatch(function(e) {
|
||||
E.showMessage('camShot !');
|
||||
Bangle.buzz(300, 1);
|
||||
setTimeout(drawApp, 1000);
|
||||
camShot(() => {});
|
||||
}, BTN2, { edge:"falling",repeat:true,debounce:50});
|
||||
} else { // Bangle.js 2
|
||||
setWatch(function(e) {
|
||||
E.showMessage('camShot !');
|
||||
Bangle.buzz(300, 1);
|
||||
setTimeout(drawApp, 1000);
|
||||
camShot(() => {});
|
||||
}, BTN, { edge:"falling",repeat:true,debounce:50});
|
||||
Bangle.on('touch', function (wat, tap) {
|
||||
E.showMessage('camShot !');
|
||||
Bangle.buzz(300, 1);
|
||||
setTimeout(drawApp, 1000);
|
||||
camShot(() => {});
|
||||
});
|
||||
}
|
||||
drawApp();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
"id": "hidcam",
|
||||
"name": "Camera shutter",
|
||||
"shortName": "Cam shutter",
|
||||
"version": "0.03",
|
||||
"version": "0.04",
|
||||
"description": "Enable HID, connect to your phone, start your camera and trigger the shot on your Bangle",
|
||||
"icon": "app.png",
|
||||
"tags": "bluetooth,tool",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"hidcam.app.js","url":"app.js"},
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
...
|
||||
0.10: First update with ChangeLog Added
|
|
@ -2,7 +2,7 @@
|
|||
{ "id": "homework",
|
||||
"name": "Homework",
|
||||
"shortName":"Homework",
|
||||
"version":"0.1",
|
||||
"version":"0.10",
|
||||
"description": "A simple app to manage homework",
|
||||
"icon": "app.png",
|
||||
"tags": "tool",
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
0.01: New App!
|
||||
0.02: Show status info on display
|
||||
Allow recording to Bangle
|
||||
0.03: Allow downloading recorded files
|
||||
Make it work with more BTHRM configs
|
||||
|
|
|
@ -6,7 +6,7 @@ This app can use [BTHRM](https://banglejs.com/apps/#bthrm) as a reference.
|
|||
## Steps for usage
|
||||
|
||||
* (Optional) Install [BTHRM](https://banglejs.com/apps/#bthrm) as reference (use ECG based sensor for best accuracy).
|
||||
* Configure BTHRM to "Both"-Mode. This prevents data beeing lost because BTHRM can replace the HRM-events data with BTHRM data.
|
||||
* Configure BTHRM to "Both"-Mode or use a version >= 0.12. This prevents data beeing lost because BTHRM can replace the HRM-events data with BTHRM data.
|
||||
* Click "Start" in browser.
|
||||
* Wait until the "Events" number starts to grow, that means there are events recorded.
|
||||
* Record for some time, since BTHRM and HRM often need some seconds to start getting useful values. Consider 2000 events a useful minimum.
|
||||
|
|
|
@ -1,18 +1,42 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Bangle.js Accelerometer streaming</title>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<script src="https://www.puck-js.com/puck.js"></script>
|
||||
<p></div><input type="checkbox" id="chkLocal">Store on Bangle (file named log.csv, download with IDE)</input></p>
|
||||
<p>
|
||||
<button id="btnConnect">Start</button>
|
||||
<button id="btnStop">Stop</button>
|
||||
<button id="btnReset">Reset</button>
|
||||
<button id="btnSave">Save CSV</button>
|
||||
<div class="form-group">
|
||||
<label class="form-switch">
|
||||
<input type="checkbox" id="chkLocal">
|
||||
<i class="form-icon"></i> Store on bangle (file named log.csv)
|
||||
</label>
|
||||
</div>
|
||||
</p>
|
||||
<p>
|
||||
<button id="btnConnect" class="btn btn-primary">Start</button>
|
||||
<button id="btnStop" class="btn btn-secondary">Stop</button>
|
||||
<button id="btnReset" class="btn btn-secondary">Reset</button>
|
||||
<button id="btnSave" class="btn btn-primary">Save CSV</button>
|
||||
<button id="btnDownload" class="btn btn-primary">Download CSV</button>
|
||||
</p>
|
||||
<p id="result"></p>
|
||||
<script>
|
||||
|
||||
function saveCSV(filename, csvData) {
|
||||
let a = document.createElement("a"),
|
||||
file = new Blob([csvData], {type: "Comma-separated value file"});
|
||||
let url = URL.createObjectURL(file);
|
||||
a.href = url;
|
||||
a.download = filename+".csv";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
setTimeout(function() {
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(url);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function createCode(){
|
||||
//modes: 1 BT, 2 File
|
||||
return "var method=" + (document.getElementById("chkLocal").checked ? 2 : 1) + ";\n" + String.raw`
|
||||
|
@ -32,7 +56,12 @@ function createCode(){
|
|||
return gotBTHRM && gotHRM && gotHRMraw && gotAcc;
|
||||
}
|
||||
|
||||
let bthrmSettings = (require("Storage").readJSON("bthrm.json",1) || {});
|
||||
|
||||
Bangle.setHRMPower(1);
|
||||
|
||||
if (bthrmSettings.replace) Bangle.origSetHRMPower(1);
|
||||
|
||||
if (Bangle.setBTHRMPower){
|
||||
Bangle.setBTHRMPower(1);
|
||||
} else {
|
||||
|
@ -99,7 +128,11 @@ function createCode(){
|
|||
Bangle.on("accel", writeAccDirect);
|
||||
}
|
||||
Bangle.on("HRM-raw", writeHRMraw);
|
||||
Bangle.on("HRM", writeHRM);
|
||||
if (bthrmSettings.replace){
|
||||
Bangle.origOn("HRM", writeHRM);
|
||||
} else {
|
||||
Bangle.on("HRM", writeHRM);
|
||||
}
|
||||
Bangle.on("BTHRM", writeBTHRM);
|
||||
|
||||
g.clear();
|
||||
|
@ -164,7 +197,6 @@ function createCode(){
|
|||
var connection;
|
||||
var lineCount=-1;
|
||||
|
||||
|
||||
function stop (){
|
||||
connection.reconnect((c)=>{
|
||||
c.write("load();\n");
|
||||
|
@ -173,32 +205,74 @@ function stop (){
|
|||
});
|
||||
}
|
||||
|
||||
document.getElementById("chkLocal").addEventListener("click", function() {
|
||||
function updateButtons(){
|
||||
document.getElementById("btnSave").disabled = document.getElementById("chkLocal").checked;
|
||||
document.getElementById("btnDownload").disabled = !document.getElementById("chkLocal").checked;
|
||||
document.getElementById("btnReset").disabled = document.getElementById("chkLocal").checked;
|
||||
document.getElementById("btnStop").disabled = document.getElementById("chkLocal").checked;
|
||||
}
|
||||
|
||||
updateButtons();
|
||||
|
||||
document.getElementById("chkLocal").addEventListener("click", function() {
|
||||
reset();
|
||||
updateButtons();
|
||||
});
|
||||
|
||||
window.addEventListener("message", function(event) {
|
||||
let msg = event.data;
|
||||
if (msg.type=="readstoragefilersp") {
|
||||
saveCSV("log.csv", msg.data);
|
||||
}
|
||||
}, false);
|
||||
|
||||
document.getElementById("btnDownload").addEventListener("click", function() {
|
||||
if (connection) {
|
||||
stop();
|
||||
}
|
||||
console.log("Loading data from BangleJs...");
|
||||
try {
|
||||
window.postMessage({type:"readstoragefile",data:"log.csv",id:0});
|
||||
} catch(ex) {
|
||||
console.log("(Warning) Could not load apikey from BangleJs.");
|
||||
console.log(ex);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("btnSave").addEventListener("click", function() {
|
||||
var h = document.createElement('a');
|
||||
h.href = 'data:text/csv;charset=utf-8,' + encodeURI(localStorage.getItem("data"));
|
||||
h.target = '_blank';
|
||||
h.download = "DATA.csv";
|
||||
h.click();
|
||||
saveCSV("log.csv", localStorage.getItem("data"));
|
||||
});
|
||||
|
||||
function reset(){
|
||||
document.getElementById("result").innerText="";
|
||||
}
|
||||
|
||||
document.getElementById("btnReset").addEventListener("click", function() {
|
||||
if (connection) {
|
||||
stop();
|
||||
}
|
||||
document.getElementById("result").innerText="";
|
||||
lineCount=-1;
|
||||
localStorage.removeItem("data");
|
||||
reset();
|
||||
});
|
||||
|
||||
document.getElementById("btnStop").addEventListener("click", function() {
|
||||
if (connection) {
|
||||
stop();
|
||||
}
|
||||
});
|
||||
|
||||
function connect(connectionHandler){
|
||||
Puck.connect(function(c) {
|
||||
if (!c) {
|
||||
console.log("Couldn't connect!\n");
|
||||
return;
|
||||
}
|
||||
connection = c;
|
||||
connectionHandler(c);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById("btnConnect").addEventListener("click", function() {
|
||||
localStorage.setItem("data", "");
|
||||
lineCount=-1;
|
||||
|
@ -206,12 +280,7 @@ document.getElementById("btnConnect").addEventListener("click", function() {
|
|||
stop();
|
||||
document.getElementById("result").innerText="0";
|
||||
}
|
||||
Puck.connect(function(c) {
|
||||
if (!c) {
|
||||
console.log("Couldn't connect!\n");
|
||||
return;
|
||||
}
|
||||
connection = c;
|
||||
connect(function(connection) {
|
||||
var buf = "";
|
||||
connection.on("data", function(d) {
|
||||
buf += d;
|
||||
|
@ -220,7 +289,7 @@ document.getElementById("btnConnect").addEventListener("click", function() {
|
|||
l.forEach(onLine);
|
||||
});
|
||||
connection.write("reset();\n", function() {
|
||||
setTimeout(function() {
|
||||
setTimeout(function() {
|
||||
connection.write("\x03\x10if(1){"+createCode()+"}\n",
|
||||
function() { console.log("Ready..."); });
|
||||
}, 1500);
|
||||
|
|
|
@ -2,14 +2,14 @@
|
|||
"id": "hrmaccevents",
|
||||
"name": "HRM Accelerometer event recorder",
|
||||
"shortName": "HRM ACC recorder",
|
||||
"version": "0.02",
|
||||
"version": "0.03",
|
||||
"type": "RAM",
|
||||
"description": "Record HRM and accelerometer events in high resolution to CSV files in your browser",
|
||||
"icon": "app.png",
|
||||
"tags": "debug",
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"custom": "custom.html",
|
||||
"customConnect": false,
|
||||
"customConnect": true,
|
||||
"readme": "README.md",
|
||||
"storage": [ ]
|
||||
}
|
||||
|
|
|
@ -6,3 +6,4 @@
|
|||
0.20: Add theme support
|
||||
0.21: Add Settings
|
||||
0.22: Use default Bangle formatter for booleans
|
||||
0.23: Added note to configure position in "my location" if not done yet. Small fixes.
|
||||
|
|
|
@ -8,6 +8,8 @@ If watch is locked, seconds get refreshed every 10 seconds.
|
|||
|
||||
## Usage
|
||||
|
||||
Location for sun set / rise set with mylocation app.
|
||||
|
||||
Provide names and the UTC offsets for up to three other timezones in the app store. These are stored in a json file on your watch. UTC offsets can be decimal (e.g., 5.5 for India).
|
||||
|
||||
The clock does not handle summer time / daylight saving time changes automatically. If one of your three locations changes its UTC offset, you can simply change the setting in the app store and update. Currently the clock only supports 24 hour time format for the additional time zones.
|
||||
|
@ -21,11 +23,5 @@ Please use [the Espruino Forum](http://forum.espruino.com/microcosms/1424/) if y
|
|||
|
||||
Created by Hank.
|
||||
|
||||
Based on the great work of
|
||||
=================
|
||||
World Clock - 4 time zones
|
||||
Made by [Scott Hale](https://www.github.com/computermacgyver), based upon the [Simple Clock](https://github.com/espruino/BangleApps/tree/master/apps/sclock).
|
||||
===== a n d =====
|
||||
Sun Clock
|
||||
[Sun Clock](https://github.com/espruino/BangleApps/tree/master/apps/sunclock)
|
||||
=================
|
||||
Based on the great work of "World Clock - 4 time zones". Made by [Scott Hale](https://www.github.com/computermacgyver), based upon the [Simple Clock](https://github.com/espruino/BangleApps/tree/master/apps/sclock).
|
||||
And Sun Clock [Sun Clock](https://github.com/espruino/BangleApps/tree/master/apps/sunclock)
|
|
@ -46,10 +46,10 @@ setting = require("Storage").readJSON("setting.json",1);
|
|||
E.setTimeZone(setting.timezone); // timezone = 1 for MEZ, = 2 for MESZ
|
||||
SunCalc = require("hsuncalc.js");
|
||||
const LOCATION_FILE = "mylocation.json";
|
||||
var rise = "07:00";
|
||||
var set = "20:00";
|
||||
var pos = {altitude: 20, azimuth: 135};
|
||||
var noonpos = {altitude: 37, azimuth: 180};
|
||||
var rise = "read";
|
||||
var set = "...";
|
||||
//var pos = {altitude: 20, azimuth: 135};
|
||||
//var noonpos = {altitude: 37, azimuth: 180};
|
||||
//=======Sun
|
||||
|
||||
var ampm = "AM";
|
||||
|
@ -113,19 +113,19 @@ g.setBgColor(g.theme.bg);
|
|||
function queueDraw() {
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = setTimeout(function() {
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
drawTimeout = undefined;
|
||||
draw();
|
||||
}, 60000 - (Date.now() % 60000));
|
||||
}
|
||||
|
||||
// schedule a draw for the next second
|
||||
function queueDrawSeconds() {
|
||||
if (drawTimeoutSeconds) clearTimeout(drawTimeoutSeconds);
|
||||
drawTimeoutSeconds = setTimeout(function() {
|
||||
drawTimeoutSeconds = undefined;
|
||||
drawSeconds();
|
||||
//console.log("TO: " + secondsTimeout);
|
||||
}, secondsTimeout - (Date.now() % secondsTimeout));
|
||||
drawTimeoutSeconds = undefined;
|
||||
drawSeconds();
|
||||
//console.log("TO: " + secondsTimeout);
|
||||
}, secondsTimeout - (Date.now() % secondsTimeout));
|
||||
}
|
||||
|
||||
function doublenum(x) {
|
||||
|
@ -137,12 +137,17 @@ function getCurrentTimeFromOffset(dt, offset) {
|
|||
}
|
||||
|
||||
function updatePos() {
|
||||
coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":53.3,"lon":10.1,"location":"Pattensen"};
|
||||
pos = SunCalc.getPosition(Date.now(), coord.lat, coord.lon);
|
||||
coord = require("Storage").readJSON(LOCATION_FILE,1)|| {"lat":0,"lon":0,"location":"-"}; //{"lat":53.3,"lon":10.1,"location":"Pattensen"};
|
||||
if (coord.lat != 0 && coord.lon != 0) {
|
||||
//pos = SunCalc.getPosition(Date.now(), coord.lat, coord.lon);
|
||||
times = SunCalc.getTimes(Date.now(), coord.lat, coord.lon);
|
||||
rise = times.sunrise.toString().split(" ")[4].substr(0,5);
|
||||
set = times.sunset.toString().split(" ")[4].substr(0,5);
|
||||
noonpos = SunCalc.getPosition(times.solarNoon, coord.lat, coord.lon);
|
||||
rise = "^" + times.sunrise.toString().split(" ")[4].substr(0,5);
|
||||
set = "v" + times.sunset.toString().split(" ")[4].substr(0,5);
|
||||
//noonpos = SunCalc.getPosition(times.solarNoon, coord.lat, coord.lon);
|
||||
} else {
|
||||
rise = null;
|
||||
set = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -152,11 +157,7 @@ function drawSeconds() {
|
|||
var da = d.toString().split(" ");
|
||||
|
||||
// default draw styles
|
||||
g.reset();
|
||||
g.setBgColor(g.theme.bg);
|
||||
|
||||
// drawSting centered
|
||||
g.setFontAlign(0, 0);
|
||||
g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0);
|
||||
|
||||
// draw time
|
||||
var time = da[4].split(":");
|
||||
|
@ -187,11 +188,7 @@ function draw() {
|
|||
var da = d.toString().split(" ");
|
||||
|
||||
// default draw styles
|
||||
g.reset();
|
||||
g.setBgColor(g.theme.bg);
|
||||
|
||||
// drawSting centered
|
||||
g.setFontAlign(0, 0);
|
||||
g.reset().setBgColor(g.theme.bg).setFontAlign(0, 0);
|
||||
|
||||
// draw time
|
||||
var time = da[4].split(":");
|
||||
|
@ -255,35 +252,31 @@ function draw() {
|
|||
var date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)];
|
||||
// For a single secondary timezone, draw it bigger and drop time zone to second line
|
||||
const xOffset = 30;
|
||||
g.setFont(font, secondaryTimeFontSize);
|
||||
g.drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
|
||||
g.setFont(font, secondaryTimeZoneFontSize);
|
||||
g.drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime2 + 30, true);
|
||||
g.setFont(font, secondaryTimeFontSize).drawString(`${hours}:${minutes}`, xyCenter, yposTime2, true);
|
||||
g.setFont(font, secondaryTimeZoneFontSize).drawString(offset[OFFSET_TIME_ZONE], xyCenter, yposTime2 + 30, true);
|
||||
|
||||
// draw Day, name of month, Date
|
||||
g.setFont(font, secondaryTimeZoneFontSize);
|
||||
g.drawString(date, xyCenter, yposDate, true);
|
||||
g.setFont(font, secondaryTimeZoneFontSize).drawString(date, xyCenter, yposDate, true);
|
||||
} else if (index < 3) {
|
||||
// For > 1 extra timezones, render as columns / rows
|
||||
g.setFont(font, secondaryRowColFontSize);
|
||||
g.setFontAlign(-1, 0);
|
||||
g.setFont(font, secondaryRowColFontSize).setFontAlign(-1, 0);
|
||||
g.drawString(
|
||||
offset[OFFSET_TIME_ZONE],
|
||||
xcol1,
|
||||
yposWorld + index * 15,
|
||||
true
|
||||
);
|
||||
g.setFontAlign(1, 0);
|
||||
g.drawString(`${hours}:${minutes}`, xcol2, yposWorld + index * 15, true);
|
||||
g.setFontAlign(1, 0).drawString(`${hours}:${minutes}`, xcol2, yposWorld + index * 15, true);
|
||||
}
|
||||
});
|
||||
|
||||
if (showSunInfo) {
|
||||
g.setFontAlign(-1, 0);
|
||||
g.setFont("Vector",12);
|
||||
g.drawString(`^${rise}`, 10, 3 + yposWorld + 3 * 15, true); // draw riseset
|
||||
g.setFontAlign(1, 0);
|
||||
g.drawString(`v${set}`, xcol2, 3 + yposWorld + 3 * 15, true); // draw riseset
|
||||
if (rise != null){
|
||||
g.setFontAlign(-1, 0).setFont("Vector",12).drawString(`${rise}`, 10, 3 + yposWorld + 3 * 15, true); // draw rise
|
||||
g.setFontAlign(1, 0).drawString(`${set}`, xcol2, 3 + yposWorld + 3 * 15, true); // draw set
|
||||
} else {
|
||||
g.setFontAlign(-1, 0).setFont("Vector",11).drawString("set city in \'my location\' app!", 10, 3 + yposWorld + 3 * 15, true);
|
||||
}
|
||||
}
|
||||
//debug settings
|
||||
//g.setFontAlign(1, 0);
|
||||
|
@ -291,7 +284,6 @@ function draw() {
|
|||
//g.drawString(showSunInfo, xcol2, 3 + yposWorld + 3 * 15, true);
|
||||
//g.drawString(colorWhenDark, xcol2, 3 + yposWorld + 3 * 15, true);
|
||||
|
||||
|
||||
queueDraw();
|
||||
|
||||
if (secondsMode != "none") queueDrawSeconds();
|
||||
|
@ -317,6 +309,7 @@ if (!Bangle.isLocked()) { // Initial state
|
|||
if (showSunInfo) {
|
||||
if (PosInterval != 0) clearInterval(PosInterval);
|
||||
PosInterval = setInterval(updatePos, 60*10E3); // refesh every 10 mins
|
||||
updatePos();
|
||||
}
|
||||
|
||||
secondsTimeout = 1000;
|
||||
|
@ -326,9 +319,8 @@ if (!Bangle.isLocked()) { // Initial state
|
|||
}
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
|
||||
draw(); // draw immediately, queue redraw
|
||||
if (showSunInfo) updatePos();
|
||||
|
||||
}else{
|
||||
if (secondsMode == "always") secondsTimeout = 1000;
|
||||
if (secondsMode == "when unlocked") secondsTimeout = 10 * 1000;
|
||||
|
@ -343,20 +335,19 @@ if (!Bangle.isLocked()) { // Initial state
|
|||
if (showSunInfo) {
|
||||
if (PosInterval != 0) clearInterval(PosInterval);
|
||||
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
|
||||
updatePos();
|
||||
}
|
||||
draw(); // draw immediately, queue redraw
|
||||
if (showSunInfo) updatePos();
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Bangle.on('lock',on=>{
|
||||
if (!on) { // UNlocked
|
||||
if (showSunInfo) {
|
||||
if (PosInterval != 0) clearInterval(PosInterval);
|
||||
PosInterval = setInterval(updatePos, 60*10E3); // refesh every 10 mins
|
||||
updatePos();
|
||||
}
|
||||
|
||||
secondsTimeout = 1000;
|
||||
|
@ -368,7 +359,6 @@ Bangle.on('lock',on=>{
|
|||
drawTimeout = undefined;
|
||||
|
||||
draw(); // draw immediately, queue redraw
|
||||
if (showSunInfo) updatePos();
|
||||
}else{ // locked
|
||||
|
||||
if (secondsMode == "always") secondsTimeout = 1000;
|
||||
|
@ -381,9 +371,11 @@ Bangle.on('lock',on=>{
|
|||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
|
||||
if (PosInterval != 0) clearInterval(PosInterval);
|
||||
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
|
||||
if (showSunInfo) {
|
||||
if (PosInterval != 0) clearInterval(PosInterval);
|
||||
PosInterval = setInterval(updatePos, 60*60E3); // refesh every 60 mins
|
||||
updatePos();
|
||||
}
|
||||
draw(); // draw immediately, queue redraw
|
||||
if (showSunInfo) updatePos();
|
||||
}
|
||||
});
|
|
@ -30,8 +30,8 @@
|
|||
}
|
||||
} catch(e){
|
||||
offsets=[
|
||||
[true,"London",0],
|
||||
[true,"NY",-5],
|
||||
[true,"London",1],
|
||||
[true,"NY",-4],
|
||||
[true, "Denver",-6],
|
||||
|
||||
];
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"id": "hworldclock",
|
||||
"name": "Hanks World Clock",
|
||||
"shortName": "Hanks World Clock",
|
||||
"version": "0.22",
|
||||
"version": "0.23",
|
||||
"description": "Current time zone plus up to three others",
|
||||
"allow_emulator":true,
|
||||
"icon": "app.png",
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
0.01: New keyboard
|
||||
0.02: Introduce setting "Show help button?". Make setting firstLaunch invisible by removing corresponding code from settings.js. Add marker that shows when character selection timeout has run out. Display opened text on launch when editing existing text string. Perfect horizontal alignment of buttons. Tweak help message letter casing.
|
||||
0.03: Use default Bangle formatter for booleans
|
||||
0.04: Allow moving the cursor
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
A library that provides the ability to input text in a style familiar to anyone who had a mobile phone before they went all touchscreen.
|
||||
|
||||
Swipe right for Space, left for Backspace, and up/down for Caps lock. Tap the '?' button in the app if you need a reminder!
|
||||
Swipe right for Space, left for Backspace, down for cursor moving mode, and up for Caps lock. Swipe left and right to move the cursor in moving mode. Tap the '?' button in the app if you need a reminder!
|
||||
|
||||
At time of writing, only the [Noteify app](http://microco.sm/out/Ffe9i) uses a keyboard.
|
||||
|
||||
|
|
|
@ -17,18 +17,50 @@ exports.input = function(options) {
|
|||
"4":"GHI4","5":"JKL5","6":"MNO6",
|
||||
"7":"PQRS7","8":"TUV80","9":"WXYZ9",
|
||||
};
|
||||
var helpMessage = 'Swipe:\nRight: Space\nLeft:Backspace\nUp/Down: Caps lock\n';
|
||||
var helpMessage = 'Swipe:\nRight: Space\nLeft:Backspace\nUp: Caps lock\nDown:Move mode';
|
||||
|
||||
var charTimeout; // timeout after a key is pressed
|
||||
var charCurrent; // current character (index in letters)
|
||||
var charIndex; // index in letters[charCurrent]
|
||||
var textIndex = text.length;
|
||||
var textWidth = settings.showHelpBtn ? 10 : 14;
|
||||
var caps = true;
|
||||
var layout;
|
||||
var btnWidth = g.getWidth()/3
|
||||
var btnWidth = g.getWidth()/3;
|
||||
|
||||
function getMoveChar(){
|
||||
return "\x00\x0B\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00@\x1F\xE1\x00\x10\x00\x10\x01\x0F\xF0\x04\x01\x00";
|
||||
}
|
||||
|
||||
function getMoreChar(){
|
||||
return "\x00\x0B\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xDB\x1B`\x00\x00\x00";
|
||||
}
|
||||
|
||||
|
||||
function getCursorChar(){
|
||||
return "\x00\x0B\x11\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xAA\xAA\x80"; }
|
||||
|
||||
function displayText(hideMarker) {
|
||||
layout.clear(layout.text);
|
||||
layout.text.label = text.slice(settings.showHelpBtn ? -11 : -13) + (hideMarker ? " " : "_");
|
||||
|
||||
let charsBeforeCursor = textIndex;
|
||||
let charsAfterCursor = Math.min(text.length - textIndex, (textWidth)/2);
|
||||
|
||||
|
||||
let start = textIndex - Math.ceil(textWidth - charsAfterCursor);
|
||||
let startMore = false;
|
||||
if (start > 0) {start++; startMore = true}
|
||||
if (start < 0) start = 0;
|
||||
let cursor = textIndex + 1;
|
||||
|
||||
let end = cursor + Math.floor(start + textWidth - cursor);
|
||||
if (end <= text.length) {end--; if (startMore) end--;}
|
||||
if (end > text.length) end = text.length;
|
||||
|
||||
let pre = (start > 0 ? getMoreChar() : "") + text.slice(start, cursor);
|
||||
let post = text.slice(cursor, end) + (end < text.length - 1 ? getMoreChar() : "");
|
||||
|
||||
layout.text.label = pre + (hideMarker ? " " : (moveMode? getMoveChar():getCursorChar())) + post;
|
||||
layout.render(layout.text);
|
||||
}
|
||||
|
||||
|
@ -41,8 +73,11 @@ exports.input = function(options) {
|
|||
|
||||
function backspace() {
|
||||
deactivateTimeout(charTimeout);
|
||||
text = text.slice(0, -1);
|
||||
newCharacter();
|
||||
if (textIndex > -1){
|
||||
text = text.slice(0, textIndex) + text.slice(textIndex + 1);
|
||||
if (textIndex > -1) textIndex --;
|
||||
newCharacter();
|
||||
}
|
||||
}
|
||||
|
||||
function setCaps() {
|
||||
|
@ -55,6 +90,7 @@ exports.input = function(options) {
|
|||
|
||||
function newCharacter(ch) {
|
||||
displayText();
|
||||
if (ch && textIndex < text.length) textIndex ++;
|
||||
charCurrent = ch;
|
||||
charIndex = 0;
|
||||
}
|
||||
|
@ -69,7 +105,11 @@ exports.input = function(options) {
|
|||
newCharacter(key);
|
||||
}
|
||||
var newLetter = letters[charCurrent][charIndex];
|
||||
text += (caps ? newLetter.toUpperCase() : newLetter.toLowerCase());
|
||||
let pre = text.slice(0, textIndex);
|
||||
let post = text.slice(textIndex, text.length);
|
||||
|
||||
text = pre + (caps ? newLetter.toUpperCase() : newLetter.toLowerCase()) + post;
|
||||
|
||||
// set a timeout
|
||||
charTimeout = setTimeout(function() {
|
||||
charTimeout = undefined;
|
||||
|
@ -78,14 +118,29 @@ exports.input = function(options) {
|
|||
displayText(charTimeout);
|
||||
}
|
||||
|
||||
var moveMode = false;
|
||||
|
||||
function onSwipe(dirLeftRight, dirUpDown) {
|
||||
if (dirUpDown) {
|
||||
if (dirUpDown == -1) {
|
||||
setCaps();
|
||||
} else if (dirUpDown == 1) {
|
||||
moveMode = !moveMode;
|
||||
displayText(false);
|
||||
} else if (dirLeftRight == 1) {
|
||||
text += ' ';
|
||||
newCharacter();
|
||||
if (!moveMode){
|
||||
text = text.slice(0, textIndex + 1) + " " + text.slice(++textIndex);
|
||||
newCharacter();
|
||||
} else {
|
||||
if (textIndex < text.length) textIndex++;
|
||||
displayText(false);
|
||||
}
|
||||
} else if (dirLeftRight == -1) {
|
||||
backspace();
|
||||
if (!moveMode){
|
||||
backspace();
|
||||
} else {
|
||||
if (textIndex > -1) textIndex--;
|
||||
displayText(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{ "id": "kbmulti",
|
||||
"name": "Multitap keyboard",
|
||||
"version":"0.03",
|
||||
"version":"0.04",
|
||||
"description": "A library for text input via multitap/T9 style keypad",
|
||||
"icon": "app.png",
|
||||
"type":"textinput",
|
||||
|
|
|
@ -97,6 +97,25 @@ var locales = {
|
|||
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
|
||||
// No translation for english...
|
||||
},
|
||||
"en_IE": {
|
||||
lang: "en_IE",
|
||||
decimal_point: ".",
|
||||
thousands_sep: ",",
|
||||
currency_symbol: "€",
|
||||
int_curr_symbol: "EUR",
|
||||
currency_first: true,
|
||||
speed: 'kmh',
|
||||
distance: { "0": "m", "1": "km" },
|
||||
temperature: '°C',
|
||||
ampm: { 0: "am", 1: "pm" },
|
||||
timePattern: { 0: "%HH:%MM:%SS ", 1: "%HH:%MM" },
|
||||
datePattern: { 0: "%d %b %Y", 1: "%d/%m/%Y" }, // 28 Feb 2020" // "28/03/2020"(short)
|
||||
abmonth: "Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec",
|
||||
month: "January,February,March,April,May,June,July,August,September,October,November,December",
|
||||
abday: "Sun,Mon,Tue,Wed,Thu,Fri,Sat",
|
||||
day: "Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday",
|
||||
// No translation for english...
|
||||
},
|
||||
"en_NAV": { // navigation units nautical miles and knots
|
||||
lang: "en_NAV",
|
||||
decimal_point: ".",
|
||||
|
|
|
@ -54,5 +54,5 @@
|
|||
0.39: Set default color for message icons according to theme
|
||||
0.40: Use default Bangle formatter for booleans
|
||||
0.41: Add notification icons in the widget
|
||||
0.42: Separate buzz pattern for incoming calls
|
||||
Fix messages ignoring "Vibrate: Off" setting
|
||||
0.42: Fix messages ignoring "Vibrate: Off" setting
|
||||
0.43: Separate buzz pattern for incoming calls
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "messages",
|
||||
"name": "Messages",
|
||||
"version": "0.42",
|
||||
"version": "0.43",
|
||||
"description": "App to display notifications from iOS and Gadgetbridge/Android",
|
||||
"icon": "app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Remove one line of code that didn't do anything other than in some instances hinder the function of the app.
|
||||
|
|
|
@ -6,9 +6,9 @@ Making the music controls accessible this way lets one start a music stream on t
|
|||
|
||||
It is suggested to use Messages Music along side the app Quick Launch.
|
||||
|
||||
Messages Music v0.01 has been verified to work with Messages v0.31 on Bangle.js 2 fw2v13.
|
||||
Messages Music v0.02 has been verified to work with Messages v0.41 on Bangle.js 2 fw2v14.
|
||||
|
||||
Music Messages should work with forks of the original Messages app. At least as long as functions pushMessage() in the library and showMusicMessage() in app.js hasn't been changed too much.
|
||||
Messages Music should work with forks of the original Messages app. At least as long as functions pushMessage() in the library and showMusicMessage() in app.js hasn't been changed too much.
|
||||
|
||||
Messages app is created by Gordon Williams with contributions from [Jeroen Peters](https://github.com/jeroenpeters1986).
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
let showMusic = () => {
|
||||
Bangle.CLOCK = 1; // To pass condition in messages library
|
||||
require('messages').pushMessage({"t":"add","artist":" ","album":" ","track":" ","dur":0,"c":-1,"n":-1,"id":"music","title":"Music","state":"play","new":true});
|
||||
Bangle.CLOCK = undefined;
|
||||
};
|
||||
|
||||
var settings = require('Storage').readJSON('messages.settings.json', true) || {}; //read settings if they exist else set to empty dict
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "messagesmusic",
|
||||
"name":"Messages Music",
|
||||
"version":"0.01",
|
||||
"version":"0.02",
|
||||
"description": "Uses Messages library to push a music message which in turn displays Messages app music controls",
|
||||
"icon":"app.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
...
|
||||
0.03: First update with ChangeLog Added
|
|
@ -0,0 +1,2 @@
|
|||
...
|
||||
0.10: First update with ChangeLog Added
|
|
@ -3,7 +3,7 @@
|
|||
"shortName":"Nova Clock",
|
||||
"icon": "app.png",
|
||||
"type": "clock",
|
||||
"version":"0.1",
|
||||
"version":"0.10",
|
||||
"description": "A clock inspired by the Kirby series",
|
||||
"tags": "clock",
|
||||
"supports": ["BANGLEJS2"],
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
|
@ -0,0 +1,13 @@
|
|||
# OpenWeatherMap weather provider
|
||||
|
||||
This updates [Weather](https://banglejs.com/apps/#weather) with data from the OpenWeatherMap API
|
||||
|
||||
## Usage
|
||||
|
||||
Just install and configure the app. This needs an internet-enabled Gadgetbridge version.
|
||||
Install [My Location](https://banglejs.com/apps/#mylocation) to change the location for the weather requests.
|
||||
Install one of the text input libraries to set the API key in the app settings or use the web interface.
|
||||
|
||||
## Creator
|
||||
|
||||
[halemmerich](https://github.com/halemmerich)
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,28 @@
|
|||
(function() {
|
||||
let waiting = false;
|
||||
let settings = require("Storage").readJSON("owmweather.json", 1) || {
|
||||
enabled: false
|
||||
};
|
||||
|
||||
function completion(){
|
||||
waiting = false;
|
||||
}
|
||||
|
||||
if (settings.enabled) {
|
||||
let weather = require("Storage").readJSON('weather.json') || {};
|
||||
let lastUpdate;
|
||||
if (weather && weather.weather && weather.weather.time) lastUpdate = weather.weather.time;
|
||||
if (!lastUpdate || lastUpdate + settings.refresh * 1000 * 60 < Date.now()){
|
||||
if (!waiting){
|
||||
waiting = true;
|
||||
require("owmweather").pull(completion);
|
||||
}
|
||||
}
|
||||
setInterval(() => {
|
||||
if (!waiting && NRF.getSecurityStatus().connected){
|
||||
waiting = true;
|
||||
require("owmweather").pull(completion);
|
||||
}
|
||||
}, settings.refresh * 1000 * 60);
|
||||
}
|
||||
})();
|
|
@ -0,0 +1 @@
|
|||
{"enabled":false,"refresh":180}
|
|
@ -0,0 +1,63 @@
|
|||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<h3>Set OpenWeatherMap (OWM) API key</h3>
|
||||
<p><input id="apikey" onkeyup="checkInput()" style="width:90%; margin: 3px"></input><button id="upload" class="btn btn-primary">Save key</button></p>
|
||||
|
||||
<h4>Where to get your personal API key?</h4>
|
||||
<p>Go to <a href="https://home.openweathermap.org/users/sign_up">https://home.openweathermap.org/users/sign_up</a> and sign up for a free account.<br>
|
||||
After registration you can login and optain your personal API key.</p>
|
||||
|
||||
|
||||
<script src="../../core/lib/interface.js"></script>
|
||||
|
||||
<script>
|
||||
|
||||
function checkInput() {
|
||||
if(document.getElementById("apikey").value==="") {
|
||||
document.getElementById('upload').disabled = true;
|
||||
} else {
|
||||
document.getElementById('upload').disabled = false;
|
||||
}
|
||||
}
|
||||
checkInput();
|
||||
|
||||
var settings = {};
|
||||
function onInit(){
|
||||
console.log("Loading settings from BangleJs...");
|
||||
try {
|
||||
Util.readStorage("owmweather.json", data=>{
|
||||
if(data.length > 0){
|
||||
settings = JSON.parse(data);
|
||||
console.log("Got settings", settings);
|
||||
document.getElementById("apikey").value = settings.apikey;
|
||||
console.log("Loaded apikey from BangleJs.");
|
||||
checkInput();
|
||||
}
|
||||
});
|
||||
} catch(ex) {
|
||||
console.log("(Warning) Could not load apikey from BangleJs.");
|
||||
console.log(ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
try {
|
||||
settings.apikey = document.getElementById("apikey").value;
|
||||
Util.showModal("Saving...");
|
||||
Util.writeStorage("owmweather.json", JSON.stringify(settings), ()=>{
|
||||
Util.hideModal();
|
||||
});
|
||||
console.log("Sent settings!");
|
||||
} catch(ex) {
|
||||
console.log("(Warning) Could not write settings to BangleJs.");
|
||||
console.log(ex);
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,53 @@
|
|||
function parseWeather(response) {
|
||||
let owmData = JSON.parse(response);
|
||||
|
||||
let isOwmData = owmData.coord && owmData.weather && owmData.main;
|
||||
|
||||
if (isOwmData) {
|
||||
let json = require("Storage").readJSON('weather.json') || {};
|
||||
let weather = {};
|
||||
weather.time = Date.now();
|
||||
weather.hum = owmData.main.humidity;
|
||||
weather.temp = owmData.main.temp;
|
||||
weather.code = owmData.weather[0].id;
|
||||
weather.wdir = owmData.wind.deg;
|
||||
weather.wind = owmData.wind.speed;
|
||||
weather.loc = owmData.name;
|
||||
weather.txt = owmData.weather[0].main;
|
||||
|
||||
if (weather.wdir != null) {
|
||||
let deg = weather.wdir;
|
||||
while (deg < 0 || deg > 360) {
|
||||
deg = (deg + 360) % 360;
|
||||
}
|
||||
weather.wrose = ['n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw', 'n'][Math.floor((deg + 22.5) / 45)];
|
||||
}
|
||||
|
||||
json.weather = weather;
|
||||
require("Storage").writeJSON('weather.json', json);
|
||||
require("weather").emit("update", json.weather);
|
||||
return undefined;
|
||||
} else {
|
||||
return /*LANG*/"Not OWM data";
|
||||
}
|
||||
}
|
||||
|
||||
exports.pull = function(completionCallback) {
|
||||
let location = require("Storage").readJSON("mylocation.json", 1) || {
|
||||
"lat": 51.50,
|
||||
"lon": 0.12,
|
||||
"location": "London"
|
||||
};
|
||||
let settings = require("Storage").readJSON("owmweather.json", 1);
|
||||
let uri = "https://api.openweathermap.org/data/2.5/weather?lat=" + location.lat.toFixed(2) + "&lon=" + location.lon.toFixed(2) + "&exclude=hourly,daily&appid=" + settings.apikey;
|
||||
if (Bangle.http){
|
||||
Bangle.http(uri, {timeout:10000}).then(event => {
|
||||
let result = parseWeather(event.resp);
|
||||
if (completionCallback) completionCallback(result);
|
||||
}).catch((e)=>{
|
||||
if (completionCallback) completionCallback(e);
|
||||
});
|
||||
} else {
|
||||
if (completionCallback) completionCallback(/*LANG*/"No http method found");
|
||||
}
|
||||
};
|
|
@ -0,0 +1,22 @@
|
|||
{ "id": "owmweather",
|
||||
"name": "OpenWeatherMap weather provider",
|
||||
"shortName":"OWM Weather",
|
||||
"version":"0.01",
|
||||
"description": "Pulls weather from OpenWeatherMap (OWM) API",
|
||||
"icon": "app.png",
|
||||
"type": "bootloader",
|
||||
"tags": "boot,tool,weather",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"interface": "interface.html",
|
||||
"readme": "README.md",
|
||||
"data": [
|
||||
{"name":"owmweather.json"},
|
||||
{"name":"weather.json"}
|
||||
],
|
||||
"storage": [
|
||||
{"name":"owmweather.default.json","url":"default.json"},
|
||||
{"name":"owmweather.boot.js","url":"boot.js"},
|
||||
{"name":"owmweather","url":"lib.js"},
|
||||
{"name":"owmweather.settings.js","url":"settings.js"}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
(function(back) {
|
||||
function writeSettings(key, value) {
|
||||
var s = require('Storage').readJSON(FILE, true) || {};
|
||||
s[key] = value;
|
||||
require('Storage').writeJSON(FILE, s);
|
||||
readSettings();
|
||||
}
|
||||
|
||||
function readSettings(){
|
||||
settings = Object.assign(
|
||||
require('Storage').readJSON("owmweather.default.json", true) || {},
|
||||
require('Storage').readJSON(FILE, true) || {}
|
||||
);
|
||||
}
|
||||
|
||||
var FILE="owmweather.json";
|
||||
var settings;
|
||||
readSettings();
|
||||
|
||||
function buildMainMenu(){
|
||||
var mainmenu = {
|
||||
'': { 'title': 'OWM weather' },
|
||||
'< Back': back,
|
||||
"Enabled": {
|
||||
value: !!settings.enabled,
|
||||
onchange: v => {
|
||||
writeSettings("enabled", v);
|
||||
}
|
||||
},
|
||||
"Refresh every": {
|
||||
value: settings.refresh / 60,
|
||||
min: 1,
|
||||
max: 48,
|
||||
step: 1,
|
||||
format: v=>v+"h",
|
||||
onchange: v => {
|
||||
writeSettings("refresh",Math.round(v * 60));
|
||||
}
|
||||
},
|
||||
"Force refresh": ()=>{
|
||||
if (!settings.apikey){
|
||||
E.showAlert("API key is needed","Hint").then(()=>{
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
} else {
|
||||
E.showMessage("Reloading weather");
|
||||
require("owmweather").pull((e)=>{
|
||||
if (e) {
|
||||
E.showAlert(e,"Error").then(()=>{
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
} else {
|
||||
E.showAlert("Success").then(()=>{
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mainmenu["API key"] = function (){
|
||||
if (require("textinput")){
|
||||
require("textinput").input({text:settings.apikey}).then(result => {
|
||||
if (result != "") {
|
||||
print("Result is", result);
|
||||
settings.apikey = result;
|
||||
writeSettings("apikey",result);
|
||||
}
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
} else {
|
||||
E.showPrompt("Install a text input lib"),then(()=>{
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return mainmenu;
|
||||
}
|
||||
|
||||
E.showMenu(buildMainMenu());
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
0.01: first release
|
|
@ -0,0 +1,49 @@
|
|||
# Presentation Timer
|
||||
|
||||
*Forked from Stopwatch Touch*
|
||||
|
||||
Simple application to keep track of slides and
|
||||
time during a presentation. Useful for conferences,
|
||||
lectures or any presentation with a somewhat strict timing.
|
||||
|
||||
The interface is pretty simple, it shows a stopwatch
|
||||
and the number of the current slide (based on the time),
|
||||
when the time for the last slide is approaching,
|
||||
the button becomes red, when it passed,
|
||||
the time will go on for another half a minute and stop automatically.
|
||||
|
||||
The only way to upload personalized timings is
|
||||
by uploading a CSV to the bangle (i.e. from the IDE),
|
||||
in the future I'll possibly figure out a better way.
|
||||
|
||||
Each line in the file (which must be called `presentation_timer.csv`)
|
||||
contains the time in minutes at which the slide
|
||||
is supposed to finish and the slide number,
|
||||
separated by a semicolon.
|
||||
For instance the line `1.5;1` means that slide 1
|
||||
is lasting until 1 minutes 30 seconds (yes it's decimal),
|
||||
after another slide will start.
|
||||
The only requirement is that timings are increasing,
|
||||
so slides number don't have to be consecutive,
|
||||
some can be skipped and they can even be short texts
|
||||
(be careful with that, I didn't test it).
|
||||
|
||||
At the moment the app is just quick and dirty
|
||||
but it should do its job.
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Example configuration file
|
||||
|
||||
_presentation_timer.csv_
|
||||
```csv
|
||||
1.5;1
|
||||
2;2
|
||||
2.5;3
|
||||
3;4
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"id": "presentation_timer",
|
||||
"name": "Presentation Timer",
|
||||
"version": "0.01",
|
||||
"description": "A touch based presentation timer for Bangle JS 2",
|
||||
"icon": "presentation_timer.png",
|
||||
"screenshots": [{"url":"screenshot1.png"},{"url":"screenshot2.png"},{"url":"screenshot3.png"},{"url":"screenshot4.png"}],
|
||||
"tags": "tools,app",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"presentation_timer.app.js","url":"presentation_timer.app.js"},
|
||||
{"name":"presentation_timer.img","url":"presentation_timer.icon.js","evaluate":true}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,272 @@
|
|||
let w = g.getWidth();
|
||||
let h = g.getHeight();
|
||||
let tTotal = Date.now();
|
||||
let tStart = tTotal;
|
||||
let tCurrent = tTotal;
|
||||
let running = false;
|
||||
let timeY = 2*h/5;
|
||||
let displayInterval;
|
||||
let redrawButtons = true;
|
||||
const iconScale = g.getWidth() / 178; // scale up/down based on Bangle 2 size
|
||||
|
||||
// 24 pixel images, scale to watch
|
||||
// 1 bit optimal, image string, no E.toArrayBuffer()
|
||||
const pause_img = atob("GBiBAf////////////////wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP/wYP////////////////w==");
|
||||
const play_img = atob("GBjBAP//AAAAAAAAAAAIAAAOAAAPgAAP4AAP+AAP/AAP/wAP/8AP//AP//gP//gP//AP/8AP/wAP/AAP+AAP4AAPgAAOAAAIAAAAAAAAAAA=");
|
||||
const reset_img = atob("GBiBAf////////////AAD+AAB+f/5+f/5+f/5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+cA5+f/5+f/5+f/5+AAB/AAD////////////w==");
|
||||
|
||||
const margin = 0.5; //half a minute tolerance
|
||||
|
||||
//dummy default values
|
||||
var slides = [
|
||||
[0.3, 1],
|
||||
[0.5, 2],
|
||||
[0.7, 3],
|
||||
[1,4]
|
||||
];
|
||||
|
||||
function log_debug(o) {
|
||||
//console.log(o);
|
||||
}
|
||||
|
||||
//first must be a number
|
||||
function readSlides() {
|
||||
let csv = require("Storage").read("presentation_timer.csv");
|
||||
if(!csv) return;
|
||||
let lines = csv.split("\n").filter(e=>e);
|
||||
log_debug("Loading "+lines.length+" slides");
|
||||
slides = lines.map(line=>{let s=line.split(";");return [+s[0],s[1]];});
|
||||
}
|
||||
|
||||
|
||||
function timeToText(t) {
|
||||
let hrs = Math.floor(t/3600000);
|
||||
let mins = Math.floor(t/60000)%60;
|
||||
let secs = Math.floor(t/1000)%60;
|
||||
let tnth = Math.floor(t/100)%10;
|
||||
let text;
|
||||
|
||||
if (hrs === 0)
|
||||
text = ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2) + "." + tnth;
|
||||
else
|
||||
text = ("0"+hrs) + ":" + ("0"+mins).substr(-2) + ":" + ("0"+secs).substr(-2);
|
||||
|
||||
//log_debug(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
function drawButtons() {
|
||||
log_debug("drawButtons()");
|
||||
if (!running && tCurrent == tTotal) {
|
||||
bigPlayPauseBtn.draw();
|
||||
} else if (!running && tCurrent != tTotal) {
|
||||
resetBtn.draw();
|
||||
smallPlayPauseBtn.draw();
|
||||
} else {
|
||||
bigPlayPauseBtn.draw();
|
||||
}
|
||||
|
||||
redrawButtons = false;
|
||||
}
|
||||
|
||||
//not efficient but damn easy
|
||||
function findSlide(time) {
|
||||
time /= 60000;
|
||||
//change colour for the last 30 seconds
|
||||
if(time > slides[slides.length-1][0] - margin && bigPlayPauseBtn.color!="#f00") {
|
||||
bigPlayPauseBtn.color="#f00";
|
||||
drawButtons();
|
||||
}
|
||||
for(let i=0; i<slides.length; i++) {
|
||||
if(slides[i][0] > time)
|
||||
return slides[i][1];
|
||||
}
|
||||
//stop automatically
|
||||
if(time > slides[slides.length-1][0] + margin) {
|
||||
bigPlayPauseBtn.color="#0ff"; //restore
|
||||
stopTimer();
|
||||
}
|
||||
return /*LANG*/"end!";
|
||||
}
|
||||
|
||||
function drawTime() {
|
||||
log_debug("drawTime()");
|
||||
let Tt = tCurrent-tTotal;
|
||||
let Ttxt = timeToText(Tt);
|
||||
|
||||
Ttxt += "\n"+findSlide(Tt);
|
||||
// total time
|
||||
g.setFont("Vector",38); // check
|
||||
g.setFontAlign(0,0);
|
||||
g.clearRect(0, timeY - 42, w, timeY + 42);
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawString(Ttxt, w/2, timeY);
|
||||
}
|
||||
|
||||
function draw() {
|
||||
let last = tCurrent;
|
||||
if (running) tCurrent = Date.now();
|
||||
g.setColor(g.theme.fg);
|
||||
if (redrawButtons) drawButtons();
|
||||
drawTime();
|
||||
}
|
||||
|
||||
function startTimer() {
|
||||
log_debug("startTimer()");
|
||||
draw();
|
||||
displayInterval = setInterval(draw, 100);
|
||||
}
|
||||
|
||||
function stopTimer() {
|
||||
log_debug("stopTimer()");
|
||||
if (displayInterval) {
|
||||
clearInterval(displayInterval);
|
||||
displayInterval = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// BTN stop start
|
||||
function stopStart() {
|
||||
log_debug("stopStart()");
|
||||
|
||||
if (running)
|
||||
stopTimer();
|
||||
|
||||
running = !running;
|
||||
Bangle.buzz();
|
||||
|
||||
if (running)
|
||||
tStart = Date.now() + tStart- tCurrent;
|
||||
tTotal = Date.now() + tTotal - tCurrent;
|
||||
tCurrent = Date.now();
|
||||
|
||||
setButtonImages();
|
||||
redrawButtons = true;
|
||||
if (running) {
|
||||
startTimer();
|
||||
} else {
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
function setButtonImages() {
|
||||
if (running) {
|
||||
bigPlayPauseBtn.setImage(pause_img);
|
||||
smallPlayPauseBtn.setImage(pause_img);
|
||||
resetBtn.setImage(reset_img);
|
||||
} else {
|
||||
bigPlayPauseBtn.setImage(play_img);
|
||||
smallPlayPauseBtn.setImage(play_img);
|
||||
resetBtn.setImage(reset_img);
|
||||
}
|
||||
}
|
||||
|
||||
// lap or reset
|
||||
function lapReset() {
|
||||
log_debug("lapReset()");
|
||||
if (!running && tStart != tCurrent) {
|
||||
redrawButtons = true;
|
||||
Bangle.buzz();
|
||||
tStart = tCurrent = tTotal = Date.now();
|
||||
g.clearRect(0,24,w,h);
|
||||
draw();
|
||||
}
|
||||
}
|
||||
|
||||
// simple on screen button class
|
||||
function BUTTON(name,x,y,w,h,c,f,i) {
|
||||
this.name = name;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.w = w;
|
||||
this.h = h;
|
||||
this.color = c;
|
||||
this.callback = f;
|
||||
this.img = i;
|
||||
}
|
||||
|
||||
BUTTON.prototype.setImage = function(i) {
|
||||
this.img = i;
|
||||
}
|
||||
|
||||
// if pressed the callback
|
||||
BUTTON.prototype.check = function(x,y) {
|
||||
//console.log(this.name + ":check() x=" + x + " y=" + y +"\n");
|
||||
|
||||
if (x>= this.x && x<= (this.x + this.w) && y>= this.y && y<= (this.y + this.h)) {
|
||||
log_debug(this.name + ":callback\n");
|
||||
this.callback();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
BUTTON.prototype.draw = function() {
|
||||
g.setColor(this.color);
|
||||
g.fillRect(this.x, this.y, this.x + this.w, this.y + this.h);
|
||||
g.setColor("#000"); // the icons and boxes are drawn black
|
||||
if (this.img != undefined) {
|
||||
let iw = iconScale * 24; // the images were loaded as 24 pixels, we will scale
|
||||
let ix = this.x + ((this.w - iw) /2);
|
||||
let iy = this.y + ((this.h - iw) /2);
|
||||
log_debug("g.drawImage(" + ix + "," + iy + "{scale: " + iconScale + "})");
|
||||
g.drawImage(this.img, ix, iy, {scale: iconScale});
|
||||
}
|
||||
g.drawRect(this.x, this.y, this.x + this.w, this.y + this.h);
|
||||
};
|
||||
|
||||
|
||||
var bigPlayPauseBtn = new BUTTON("big",0, 3*h/4 ,w, h/4, "#0ff", stopStart, play_img);
|
||||
var smallPlayPauseBtn = new BUTTON("small",w/2, 3*h/4 ,w/2, h/4, "#0ff", stopStart, play_img);
|
||||
var resetBtn = new BUTTON("rst",0, 3*h/4, w/2, h/4, "#ff0", lapReset, pause_img);
|
||||
|
||||
bigPlayPauseBtn.setImage(play_img);
|
||||
smallPlayPauseBtn.setImage(play_img);
|
||||
resetBtn.setImage(pause_img);
|
||||
|
||||
|
||||
Bangle.on('touch', function(button, xy) {
|
||||
var x = xy.x;
|
||||
var y = xy.y;
|
||||
|
||||
// adjust for outside the dimension of the screen
|
||||
// http://forum.espruino.com/conversations/371867/#comment16406025
|
||||
if (y > h) y = h;
|
||||
if (y < 0) y = 0;
|
||||
if (x > w) x = w;
|
||||
if (x < 0) x = 0;
|
||||
|
||||
// not running, and reset
|
||||
if (!running && tCurrent == tTotal && bigPlayPauseBtn.check(x, y)) return;
|
||||
|
||||
// paused and hit play
|
||||
if (!running && tCurrent != tTotal && smallPlayPauseBtn.check(x, y)) return;
|
||||
|
||||
// paused and press reset
|
||||
if (!running && tCurrent != tTotal && resetBtn.check(x, y)) return;
|
||||
|
||||
// must be running
|
||||
if (running && bigPlayPauseBtn.check(x, y)) return;
|
||||
});
|
||||
|
||||
// Stop updates when LCD is off, restart when on
|
||||
Bangle.on('lcdPower',on=>{
|
||||
if (on) {
|
||||
draw(); // draw immediately, queue redraw
|
||||
} else { // stop draw timer
|
||||
if (drawTimeout) clearTimeout(drawTimeout);
|
||||
drawTimeout = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// Clear the screen once, at startup
|
||||
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
|
||||
// above not working, hence using next 2 lines
|
||||
g.setColor("#000");
|
||||
g.fillRect(0,0,w,h);
|
||||
|
||||
Bangle.loadWidgets();
|
||||
Bangle.drawWidgets();
|
||||
draw();
|
||||
setWatch(() => load(), BTN, { repeat: false, edge: "falling" });
|
||||
readSlides();
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwxH+AC1WwIZXACmBF7FWAH4Ae/0WAFiQBF9+sAFgv/AAvXAAgvmFgoyWF6IuLGCIvPFpoxRF5wlIwIKJF8lWwIvjQpIvKGBgv8cpWBF5QwLF/4vrEJQvNGBQv/F5FWSCq/XGAVWB5DviEgRiJF8gxDF9q+SF5owWEJYv9GCggMF5wwSD5ovPGCAeOF6AwODp4vRGJYbRF6YAbF/4v/F8eBAYYECAYYvRACFWqwEGwNWwIeSF7IEFAD5VBGhpekAo6QiEYo1LR0QpGBgyOhAxCQfKIIhFGxpegA44+HF85gRA=="))
|
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,2 @@
|
|||
...
|
||||
0.02: First update with ChangeLog Added
|
|
@ -5,4 +5,5 @@
|
|||
0.05: BUGFIX: pedometer widget interfered with the clock Font Alignment
|
||||
0.06: Use Bangle.setUI for button/launcher handling
|
||||
0.07: Support for Bangle.js 2 and themes
|
||||
0.08: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up".
|
||||
0.08: Removed "wake LCD on face-up"-feature: A watch-face should not set things like "wake LCD on face-up".
|
||||
0.09: Added button control toggle and other live controls to new settings screen.
|
||||
|
|
|
@ -6,22 +6,49 @@ Inspired by the Pebble sliding clock, old times are scrolled off the screen and
|
|||
|
||||
## Usage
|
||||
|
||||
### Button 1
|
||||
### Bangle 2
|
||||
|
||||
The Bangle 2 has Live Controls switched **off** by default so the colour and language have to be changed from the setting Menu.
|
||||
Please locate the Sliding Text clock under the setting->apps menu.
|
||||
|
||||
With the Live Controls switched on:
|
||||
#### Bottom right hand corner press
|
||||
press the bottom right hand corner of the screen to change the colour
|
||||
|
||||
| White | Black | Gray | Red |
|
||||
|----------------------|----------------------|----------------------|----------------------|
|
||||
|  |  |  |  |
|
||||
|
||||
#### Top right hand corner press
|
||||
press the top right hand corner of the screen to change the language
|
||||
|
||||
### Bangle 1
|
||||
|
||||
By Default the Live Controls (The side buttons) are switched on, which means the clock face can be controlled dynamically using the 2 side buttons on the right hand side
|
||||
|
||||
#### Button 1
|
||||
|
||||
Use Button 1 (the top right button) to change the language
|
||||
|
||||
| English | English (Traditional) | French | Japanese (Romanji) |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
|  |  |  | |
|
||||
|  |  |  | |
|
||||
| **German** | **Spanish** | | |
|
||||
|  |  | | |
|
||||
|  |  | | |
|
||||
|
||||
### Button 3
|
||||
#### Button 3
|
||||
Button 3 (bottom right button) is used to change the colour
|
||||
|
||||
| Black | Red | Gray | Purple |
|
||||
| ---- | ---- | ---- | ---- |
|
||||
|  |  |  |  |
|
||||
|  |  |  |  |
|
||||
|
||||
#### Settings
|
||||
|
||||
To turn off the Live Controls and change the settings statically please visit the settings menu. The settings menu will allow you to:
|
||||
- Colour Scheme
|
||||
- Language
|
||||
- Live Controls (On or Off)
|
||||
|
||||
## Further Details
|
||||
|
||||
|
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 24 KiB After Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
After Width: | Height: | Size: 41 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 45 KiB |
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "slidingtext",
|
||||
"name": "Sliding Clock",
|
||||
"version": "0.08",
|
||||
"version": "0.09",
|
||||
"description": "Inspired by the Pebble sliding clock, old times are scrolled off the screen and new times on. You are also able to change language on the fly so you can see the time written in other languages using button 1. Currently English, French, Japanese, Spanish and German are supported",
|
||||
"icon": "slidingtext.png",
|
||||
"type": "clock",
|
||||
|
@ -12,6 +12,7 @@
|
|||
"allow_emulator": false,
|
||||
"storage": [
|
||||
{"name":"slidingtext.app.js","url":"slidingtext.js"},
|
||||
{"name":"slidingtext.settings.js","url":"slidingtext.settings.js"},
|
||||
{"name":"slidingtext.img","url":"slidingtext-icon.js","evaluate":true},
|
||||
{"name":"slidingtext.locale.en.js","url":"slidingtext.locale.en.js"},
|
||||
{"name":"slidingtext.locale.en2.js","url":"slidingtext.locale.en2.js"},
|
||||
|
@ -21,5 +22,6 @@
|
|||
{"name":"slidingtext.locale.jp.js","url":"slidingtext.locale.jp.js"},
|
||||
{"name":"slidingtext.locale.de.js","url":"slidingtext.locale.de.js"},
|
||||
{"name":"slidingtext.dtfmt.js","url":"slidingtext.dtfmt.js"}
|
||||
]
|
||||
],
|
||||
"data": [{"name": "slidingtext.settings.json"}]
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ class DateFormatter {
|
|||
* to the lines of text on the screen
|
||||
*/
|
||||
name(){return "no name";}
|
||||
shortName(){return "no short name"}
|
||||
formatDate(date){
|
||||
return ["no","date","defined"];
|
||||
}
|
||||
|
|
|
@ -5,11 +5,17 @@
|
|||
*/
|
||||
|
||||
const color_schemes = [
|
||||
{
|
||||
name: "white",
|
||||
background : [1.0,1.0,1.0],
|
||||
main_bar: [0.0,0.0,0.0],
|
||||
other_bars: [0.1,0.1,0.1],
|
||||
},
|
||||
{
|
||||
name: "black",
|
||||
background : [0.0,0.0,0.0],
|
||||
main_bar: [1.0,1.0,1.0],
|
||||
other_bars: [0.85,0.85,0.85],
|
||||
other_bars: [0.9,0.9,0.9],
|
||||
},
|
||||
{
|
||||
name: "red",
|
||||
|
@ -81,7 +87,7 @@ function reset_commands(){
|
|||
|
||||
function has_commands(){
|
||||
return command_stack_high_priority.length > 0 ||
|
||||
command_stack_low_priority.lenth > 0;
|
||||
command_stack_low_priority.length > 0;
|
||||
}
|
||||
|
||||
class ShiftText {
|
||||
|
@ -244,15 +250,19 @@ function setRowDisplays(y, heights) {
|
|||
y += heights[i];
|
||||
}
|
||||
}
|
||||
if (g.getHeight()>200)
|
||||
|
||||
function bangleVersion(){
|
||||
return (g.getHeight()>200)? 1 : 2;
|
||||
}
|
||||
|
||||
if (bangleVersion()<2)
|
||||
setRowDisplays(50, [40,30,30,30,40]);
|
||||
else
|
||||
setRowDisplays(34, [35,25,25,25,35]);
|
||||
|
||||
function nextColorTheme(){
|
||||
//console.log("next color theme");
|
||||
color_scheme_index += 1;
|
||||
if(color_scheme_index >= row_displays.length){
|
||||
if(color_scheme_index > row_displays.length){
|
||||
color_scheme_index = 0;
|
||||
}
|
||||
setColorScheme(color_schemes[color_scheme_index]);
|
||||
|
@ -411,8 +421,7 @@ function draw_clock(){
|
|||
reset_commands();
|
||||
date = display_time(date);
|
||||
console.log("draw_clock:" + last_draw_time.toISOString() + " display:" + date.toISOString());
|
||||
// for debugging only
|
||||
//date.setMinutes(37);
|
||||
|
||||
var rows = date_formatter.formatDate(date);
|
||||
var display;
|
||||
for (var i = 0; i < rows.length; i++) {
|
||||
|
@ -495,7 +504,7 @@ function set_colorscheme(colorscheme_name){
|
|||
function set_dateformat(dateformat_name){
|
||||
console.log("setting date format:" + dateformat_name);
|
||||
for (var i=0; i < date_formatters.length; i++) {
|
||||
if(date_formatters[i].name() == dateformat_name){
|
||||
if(date_formatters[i].shortName() == dateformat_name){
|
||||
date_formatter_idx = i;
|
||||
date_formatter = date_formatters[date_formatter_idx];
|
||||
console.log("match");
|
||||
|
@ -503,6 +512,7 @@ function set_dateformat(dateformat_name){
|
|||
}
|
||||
}
|
||||
|
||||
var enable_live_controls = false;
|
||||
const PREFERENCE_FILE = "slidingtext.settings.json";
|
||||
/**
|
||||
* Called on startup to set the watch to the last preference settings
|
||||
|
@ -510,7 +520,7 @@ const PREFERENCE_FILE = "slidingtext.settings.json";
|
|||
function load_settings(){
|
||||
var setScheme = false;
|
||||
try{
|
||||
settings = require("Storage").readJSON(PREFERENCE_FILE);
|
||||
var settings = require("Storage").readJSON(PREFERENCE_FILE);
|
||||
if(settings != null){
|
||||
console.log("loaded:" + JSON.stringify(settings));
|
||||
if(settings.color_scheme != null){
|
||||
|
@ -520,9 +530,15 @@ function load_settings(){
|
|||
if(settings.date_format != null){
|
||||
set_dateformat(settings.date_format);
|
||||
}
|
||||
if(settings.enable_live_controls == null){
|
||||
settings.enable_live_controls = (bangleVersion() <= 1);
|
||||
}
|
||||
enable_live_controls = settings.enable_live_controls;
|
||||
} else {
|
||||
console.log("no settings to load");
|
||||
enable_live_controls = (bangleVersion() <= 1);
|
||||
}
|
||||
console.log("enable_live_controls=" + enable_live_controls);
|
||||
} catch(e){
|
||||
console.log("failed to load settings:" + e);
|
||||
}
|
||||
|
@ -536,24 +552,30 @@ function load_settings(){
|
|||
*/
|
||||
function save_settings(){
|
||||
var settings = {
|
||||
date_format : date_formatter.name(),
|
||||
date_format : date_formatter.shortName(),
|
||||
color_scheme : color_schemes[color_scheme_index].name,
|
||||
enable_live_controls: enable_live_controls
|
||||
};
|
||||
console.log("saving:" + JSON.stringify(settings));
|
||||
require("Storage").writeJSON(PREFERENCE_FILE,settings);
|
||||
}
|
||||
|
||||
function button1pressed() {
|
||||
changeFormatter();
|
||||
save_settings();
|
||||
console.log("button1pressed");
|
||||
if (enable_live_controls) {
|
||||
changeFormatter();
|
||||
save_settings();
|
||||
}
|
||||
}
|
||||
|
||||
function button3pressed() {
|
||||
console.log("button3pressed");
|
||||
nextColorTheme();
|
||||
reset_clock(true);
|
||||
draw_clock();
|
||||
save_settings();
|
||||
if (enable_live_controls) {
|
||||
nextColorTheme();
|
||||
reset_clock(true);
|
||||
draw_clock();
|
||||
save_settings();
|
||||
}
|
||||
}
|
||||
|
||||
// The interval reference for updating the clock
|
||||
|
|
|
@ -66,6 +66,7 @@ function germanMinsToText(mins) {
|
|||
class GermanDateFormatter extends DateFormatter {
|
||||
constructor() { super();}
|
||||
name(){return "German";}
|
||||
shortName(){return "de"}
|
||||
formatDate(date){
|
||||
var mins = date.getMinutes();
|
||||
var hourOfDay = date.getHours();
|
||||
|
|
|
@ -5,6 +5,7 @@ const numberToText = require("slidingtext.utils.en.js").numberToText;
|
|||
class EnglishDateFormatter extends DateFormatter {
|
||||
constructor() { super();}
|
||||
name(){return "English";}
|
||||
shortName(){return "en"}
|
||||
formatDate(date){
|
||||
var hours_txt = hoursToText(date.getHours());
|
||||
var mins_txt = numberToText(date.getMinutes());
|
||||
|
|
|
@ -7,6 +7,7 @@ class EnglishTraditionalDateFormatter extends DateFormatter {
|
|||
super();
|
||||
}
|
||||
name(){return "English (Traditional)";}
|
||||
shortName(){return "en2"}
|
||||
formatDate(date){
|
||||
var mins = date.getMinutes();
|
||||
var hourOfDay = date.getHours();
|
||||
|
|
|
@ -47,6 +47,7 @@ function spanishMinsToText(mins){
|
|||
class SpanishDateFormatter extends DateFormatter {
|
||||
constructor() { super();}
|
||||
name(){return "Spanish";}
|
||||
shortName(){return "es"}
|
||||
formatDate(date){
|
||||
var mins = date.getMinutes();
|
||||
var hourOfDay = date.getHours();
|
||||
|
|
|
@ -31,6 +31,7 @@ function frenchHeures(hours){
|
|||
class FrenchDateFormatter extends DateFormatter {
|
||||
constructor() { super(); }
|
||||
name(){return "French";}
|
||||
shortName(){return "fr"}
|
||||
formatDate(date){
|
||||
var hours = frenchHoursToText(date.getHours());
|
||||
var heures = frenchHeures(date.getHours());
|
||||
|
|
|
@ -61,6 +61,7 @@ function japaneseMinsToText(mins){
|
|||
class JapaneseDateFormatter extends DateFormatter {
|
||||
constructor() { super(); }
|
||||
name(){return "Japanese (Romanji)";}
|
||||
shortName(){return "jp"}
|
||||
formatDate(date){
|
||||
var hours_txt = japaneseHoursToText(date.getHours());
|
||||
var mins_txt = japaneseMinsToText(date.getMinutes());
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
(function(back) {
|
||||
const PREFERENCE_FILE = "slidingtext.settings.json";
|
||||
var settings = Object.assign({},
|
||||
require('Storage').readJSON(PREFERENCE_FILE, true) || {});
|
||||
// the screen controls are defaulted on for a bangle 1 and off for a bangle 2
|
||||
if(settings.enable_live_controls == null){
|
||||
settings.enable_live_controls = (g.getHeight()> 200);
|
||||
}
|
||||
console.log("loaded:" + JSON.stringify(settings));
|
||||
|
||||
const LANGUAGES_FILE = "slidingtext.languages.json";
|
||||
const LANGUAGES_DEFAULT = ["en","en2"];
|
||||
var locales = null;
|
||||
try {
|
||||
locales = require("Storage").readJSON(LANGUAGES_FILE);
|
||||
} catch(e) {
|
||||
console.log("failed to load languages:" + e);
|
||||
}
|
||||
if(locales == null || locales.length == 0){
|
||||
locales = LANGUAGES_DEFAULT;
|
||||
console.log("defaulting languages to locale:" + locales);
|
||||
}
|
||||
|
||||
function writeSettings() {
|
||||
console.log("saving:" + JSON.stringify(settings));
|
||||
require('Storage').writeJSON(PREFERENCE_FILE, settings);
|
||||
}
|
||||
|
||||
// Helper method which uses int-based menu item for set of string values
|
||||
function stringItems(startvalue, writer, values) {
|
||||
return {
|
||||
value: (startvalue === undefined ? 0 : values.indexOf(startvalue)),
|
||||
format: v => values[v],
|
||||
min: 0,
|
||||
max: values.length - 1,
|
||||
wrap: true,
|
||||
step: 1,
|
||||
onchange: v => {
|
||||
writer(values[v]);
|
||||
writeSettings();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Helper method which breaks string set settings down to local settings object
|
||||
function stringInSettings(name, values) {
|
||||
return stringItems(settings[name], v => settings[name] = v, values);
|
||||
}
|
||||
|
||||
// Show the menu
|
||||
E.showMenu({
|
||||
"" : { "title" : "Sliding Text" },
|
||||
"< Back" : () => back(),
|
||||
"Colour": stringInSettings("color_scheme", ["white", "black", "red","grey","purple","blue"]),
|
||||
"Languages": stringInSettings("date_format", locales),
|
||||
"Live Control": {
|
||||
value: (settings.enable_live_controls !== undefined ? settings.enable_live_controls : true),
|
||||
format: v => v ? "On" : "Off",
|
||||
onchange: v => {
|
||||
settings.enable_live_controls = v;
|
||||
writeSettings();
|
||||
}
|
||||
},
|
||||
});
|
||||
})
|
|
@ -0,0 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Fix issue setting colors after showMessage
|
|
@ -67,9 +67,9 @@ function startTimer() {
|
|||
- hint for help in state start
|
||||
*/
|
||||
function showCounter(withHint) {
|
||||
//g.clear();
|
||||
g.reset(); // workaround for E.showMessage bg color in 2v14 and earlier
|
||||
E.showMessage("", appTitle());
|
||||
g.setFontAlign(0,0); // center font
|
||||
g.reset().setFontAlign(0,0); // center font
|
||||
// draw the current counter value
|
||||
g.setBgColor(-1).setColor(0,0,1); // blue
|
||||
g.setFont("Vector",20); // vector font, 20px
|
||||
|
@ -123,9 +123,9 @@ function countUp() {
|
|||
outOfTime();
|
||||
return;
|
||||
}
|
||||
g.clear();
|
||||
g.reset(); // workaround for E.showMessage bg color in 2v14 and earlier
|
||||
E.showMessage("", appTitle());
|
||||
g.setFontAlign(0,0); // center font
|
||||
g.reset().setFontAlign(0,0); // center font
|
||||
g.setBgColor(-1).setColor(0,0,1); // blue
|
||||
g.setFont("Vector",20); // vector font, 20px
|
||||
g.drawString("Timer: " + timeFormated(counterStart),80,55);
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "teatimer",
|
||||
"name": "Tea Timer",
|
||||
"version": "0.01",
|
||||
"version": "0.02",
|
||||
"description": "A simple timer. You can easyly set up the time.",
|
||||
"icon": "teatimer.png",
|
||||
"type": "app",
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
...
|
||||
0.04: First update with ChangeLog Added
|