Merge remote-tracking branch 'upstream/master'
commit
0d8efbe015
45
apps.json
45
apps.json
|
|
@ -99,18 +99,20 @@
|
|||
"id": "android",
|
||||
"name": "Android Integration",
|
||||
"shortName": "Android",
|
||||
"version": "0.05",
|
||||
"version": "0.06",
|
||||
"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",
|
||||
"dependencies": {"messages":"app"},
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"android.app.js","url":"app.js"},
|
||||
{"name":"android.settings.js","url":"settings.js"},
|
||||
{"name":"android.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"android.boot.js","url":"boot.js"}
|
||||
],
|
||||
"data": [{"name":"android.settings.json"}],
|
||||
"sortorder": -8
|
||||
},
|
||||
{
|
||||
|
|
@ -167,7 +169,7 @@
|
|||
{
|
||||
"id": "setting",
|
||||
"name": "Settings",
|
||||
"version": "0.40",
|
||||
"version": "0.41",
|
||||
"description": "A menu for setting up Bangle.js",
|
||||
"icon": "settings.png",
|
||||
"tags": "tool,system",
|
||||
|
|
@ -1534,13 +1536,14 @@
|
|||
{
|
||||
"id": "assistedgps",
|
||||
"name": "Assisted GPS Update (AGPS)",
|
||||
"version": "0.01",
|
||||
"description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
|
||||
"version": "0.02",
|
||||
"description": "Downloads assisted GPS (AGPS) data to Bangle.js 1 or 2 for faster GPS startup and more accurate fixes. **No app will be installed**, this just uploads new data to the GPS chip.",
|
||||
"icon": "app.png",
|
||||
"type": "RAM",
|
||||
"tags": "tool,outdoors,agps",
|
||||
"supports": ["BANGLEJS"],
|
||||
"supports": ["BANGLEJS","BANGLEJS2"],
|
||||
"custom": "custom.html",
|
||||
"customConnect": true,
|
||||
"storage": []
|
||||
},
|
||||
{
|
||||
|
|
@ -4515,7 +4518,7 @@
|
|||
"name": "LCARS Clock",
|
||||
"shortName":"LCARS",
|
||||
"icon": "lcars.png",
|
||||
"version":"0.11",
|
||||
"version":"0.12",
|
||||
"readme": "README.md",
|
||||
"supports": ["BANGLEJS2"],
|
||||
"description": "Library Computer Access Retrieval System (LCARS) clock.",
|
||||
|
|
@ -5540,21 +5543,25 @@
|
|||
{"name":"limelight.img","url":"limelight.icon.js","evaluate":true}
|
||||
]
|
||||
},
|
||||
{ "id": "configurable_clock",
|
||||
"name": "Configurable Analog Clock",
|
||||
"shortName":"Configurable Clock",
|
||||
"version":"0.02",
|
||||
"description": "an analog clock with several kinds of faces, hands and colors to choose from",
|
||||
"icon": "app-icon.png",
|
||||
"type": "clock",
|
||||
"tags": "clock",
|
||||
{ "id": "banglexercise",
|
||||
"name": "BanglExercise",
|
||||
"shortName":"BanglExercise",
|
||||
"version":"0.01",
|
||||
"description": "Can automatically track exercises while wearing the Bangle.js watch.",
|
||||
"icon": "app.png",
|
||||
"screenshots": [{"url":"screenshot.png"}],
|
||||
"type": "app",
|
||||
"tags": "sport",
|
||||
"supports" : ["BANGLEJS2"],
|
||||
"allow_emulator": true,
|
||||
"screenshots": [{"url":"app-screenshot.png"}],
|
||||
"allow_emulator":true,
|
||||
"readme": "README.md",
|
||||
"storage": [
|
||||
{"name":"configurable_clock.app.js","url":"app.js"},
|
||||
{"name":"configurable_clock.img","url":"app-icon.js","evaluate":true}
|
||||
]
|
||||
{"name":"banglexercise.app.js","url":"app.js"},
|
||||
{"name":"banglexercise.img","url":"app-icon.js","evaluate":true},
|
||||
{"name":"banglexercise.settings.js","url":"settings.js"}
|
||||
],
|
||||
"data": [
|
||||
{"name":"banglexercise.json"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -4,3 +4,4 @@
|
|||
0.03: Handling of message actions (ok/clear)
|
||||
0.04: Android icon now goes to settings page with 'find phone'
|
||||
0.05: Fix handling of message actions
|
||||
0.06: Option to keep messages after a disconnect (default false) (fix #1186)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
# Android Integration
|
||||
|
||||
This app allows your Bangle.js to receive notifications [from the Gadgetbridge app on Android](http://www.espruino.com/Gadgetbridge)
|
||||
|
||||
See [this link](http://www.espruino.com/Gadgetbridge) for notes on how to install
|
||||
the Android app (and how it works).
|
||||
|
||||
It requires the `Messages` app on Bangle.js (which should be automatically installed) to
|
||||
display any notifications that are received.
|
||||
|
||||
## Settings
|
||||
|
||||
You can access the settings menu either from the `Android` icon in the launcher,
|
||||
or from `App Settings` in the `Settings` menu.
|
||||
|
||||
It contains:
|
||||
|
||||
* `Connected` - shows whether there is an active Bluetooth connection or not
|
||||
* `Find Phone` - opens a submenu where you can activate the `Find Phone` functionality
|
||||
of Gadgetbridge - making your phone make noise so you can find it.
|
||||
* `Keep Msgs` - default is `Off`. When Gadgetbridge disconnects, should Bangle.js
|
||||
keep any messages it has received, or should it delete them?
|
||||
* `Messages` - launches the messages app, showing a list of messages
|
||||
|
||||
## How it works
|
||||
|
||||
Gadgetbridge on Android connects to Bangle.js, and sends commands over the
|
||||
BLE UART connection. These take the form of `GB({ ... JSON ... })\n` - so they
|
||||
call a global function called `GB` which then interprets the JSON.
|
||||
|
||||
Responses are sent back to Gadgetbridge simply as one line of JSON.
|
||||
|
||||
More info on message formats on http://www.espruino.com/Gadgetbridge
|
||||
|
||||
## Testing
|
||||
|
||||
Bangle.js can only hold one connection open at a time, so it's hard to see
|
||||
if there are any errors when handling Gadgetbridge messages.
|
||||
|
||||
However you can:
|
||||
|
||||
* Use the `Gadgetbridge Debug` app on Bangle.js to display/log the messages received from Gadgetbridge
|
||||
* Connect with the Web IDE and manually enter the Gadgetbridge messages on the left-hand side to
|
||||
execute them as if they came from Gadgetbridge, for instance:
|
||||
|
||||
```
|
||||
GB({"t":"notify","id":1575479849,"src":"Hangouts","title":"A Name","body":"message contents"})
|
||||
```
|
||||
|
|
@ -4,6 +4,7 @@
|
|||
Bluetooth.println(JSON.stringify(message));
|
||||
}
|
||||
|
||||
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||
var _GB = global.GB;
|
||||
global.GB = (event) => {
|
||||
// feed a copy to other handlers if there were any
|
||||
|
|
@ -51,7 +52,8 @@
|
|||
// Battery monitor
|
||||
function sendBattery() { gbSend({ t: "status", bat: E.getBattery() }); }
|
||||
NRF.on("connect", () => setTimeout(sendBattery, 2000));
|
||||
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
||||
if (!settings.keep)
|
||||
NRF.on("disconnect", () => require("messages").clearAll()); // remove all messages on disconnect
|
||||
setInterval(sendBattery, 10*60*1000);
|
||||
// Health tracking
|
||||
Bangle.on('health', health=>{
|
||||
|
|
@ -68,4 +70,6 @@
|
|||
if (isFinite(msg.id)) return gbSend({ t: "notify", n:response?"OPEN":"DISMISS", id: msg.id });
|
||||
// error/warn here?
|
||||
};
|
||||
// remove settings object so it's not taking up RAM
|
||||
delete settings;
|
||||
})();
|
||||
|
|
|
|||
|
|
@ -2,17 +2,29 @@
|
|||
function gb(j) {
|
||||
Bluetooth.println(JSON.stringify(j));
|
||||
}
|
||||
var settings = require("Storage").readJSON("android.settings.json",1)||{};
|
||||
function updateSettings() {
|
||||
require("Storage").writeJSON("android.settings.json", settings);
|
||||
}
|
||||
var mainmenu = {
|
||||
"" : { "title" : "Android" },
|
||||
"< Back" : back,
|
||||
"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
|
||||
/*LANG*/"Connected" : { value : NRF.getSecurityStatus().connected?"Yes":"No" },
|
||||
"Find Phone" : () => E.showMenu({
|
||||
"" : { "title" : "Find Phone" },
|
||||
"< Back" : ()=>E.showMenu(mainmenu),
|
||||
"On" : _=>gb({t:"findPhone",n:true}),
|
||||
"Off" : _=>gb({t:"findPhone",n:false}),
|
||||
/*LANG*/"On" : _=>gb({t:"findPhone",n:true}),
|
||||
/*LANG*/"Off" : _=>gb({t:"findPhone",n:false}),
|
||||
}),
|
||||
"Messages" : ()=>load("messages.app.js")
|
||||
/*LANG*/"Keep Msgs" : {
|
||||
value : !!settings.keep,
|
||||
format : v=>v?/*LANG*/"Yes":/*LANG*/"No",
|
||||
onchange: v => {
|
||||
settings.keep = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
/*LANG*/"Messages" : ()=>load("messages.app.js")
|
||||
};
|
||||
E.showMenu(mainmenu);
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Update to work with Bangle.js 2
|
||||
|
|
|
|||
|
|
@ -8,34 +8,47 @@
|
|||
<p>GPS can take a long time (~5 minutes) to get an accurate position the first time it is used.
|
||||
AGPS uploads a few hints to the GPS receiver about satellite positions that allow it
|
||||
to get a faster, more accurate fix - however they are only valid for a short period of time.</p>
|
||||
<p>You can upload data that covers a longer period of time, but the upload will take longer.</p>
|
||||
<div class="form-group">
|
||||
<label class="form-label">AGPS Validity time</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="1d"><i class="form-icon"></i> 1 day (8kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="2d" checked><i class="form-icon"></i> 2 days (14kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="3d"><i class="form-icon"></i> 3 days (20kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="1wk"><i class="form-icon"></i> 1 week (46kB)
|
||||
</label>
|
||||
<div id="banglejs1-info" style="display:none">
|
||||
<p>You can upload data that covers a longer period of time, but the upload will take longer.</p>
|
||||
<div class="form-group">
|
||||
<label class="form-label">AGPS Validity time</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="1d"><i class="form-icon"></i> 1 day (8kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="2d" checked><i class="form-icon"></i> 2 days (14kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="3d"><i class="form-icon"></i> 3 days (20kB)
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input type="radio" name="agpsperiod" value="1wk"><i class="form-icon"></i> 1 week (46kB)
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<p>Click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
<div id="banglejs2-info" style="display:none">
|
||||
</div>
|
||||
<p id="upload-wrap" style="display:none">Click <button id="upload" class="btn btn-primary">Upload</button></p>
|
||||
|
||||
<script src="../../core/lib/customize.js"></script>
|
||||
|
||||
<script>
|
||||
var isB1; // is Bangle.js 1?
|
||||
var isB2; // is Bangle.js 2?
|
||||
|
||||
// When the 'upload' button is clicked...
|
||||
document.getElementById("upload").addEventListener("click", function() {
|
||||
var radios = document.getElementsByName('agpsperiod');
|
||||
var url = "https://www.espruino.com/agps/assistnow_1d.base64";
|
||||
for (var i=0; i<radios.length; i++)
|
||||
if (radios[i].checked)
|
||||
url = "https://www.espruino.com/agps/assistnow_"+radios[i].value+".base64";
|
||||
var url;
|
||||
if (isB1) {
|
||||
var radios = document.getElementsByName('agpsperiod');
|
||||
url = "https://www.espruino.com/agps/assistnow_1d.base64";
|
||||
for (var i=0; i<radios.length; i++)
|
||||
if (radios[i].checked)
|
||||
url = "https://www.espruino.com/agps/assistnow_"+radios[i].value+".base64";
|
||||
}
|
||||
if (isB2) {
|
||||
url = "https://www.espruino.com/agps/casic.base64";
|
||||
}
|
||||
console.log("Sending...");
|
||||
//var text = document.getElementById("agpsperiod").value;
|
||||
get(url, function(b64) {
|
||||
|
|
@ -48,6 +61,8 @@
|
|||
});
|
||||
});
|
||||
|
||||
// =================================================== Bangle.js 1 UBLOX
|
||||
|
||||
function UBX_CMD(cmd) {
|
||||
var d = [0xB5,0x62]; // sync chars
|
||||
d = d.concat(cmd);
|
||||
|
|
@ -79,13 +94,35 @@
|
|||
return UBX_CMD([].slice.call(a));
|
||||
}
|
||||
|
||||
// =================================================== Bangle.js 2 CASIC
|
||||
|
||||
function CASIC_CHECKSUM(cmd) {
|
||||
var cs = 0;
|
||||
for (var i=1;i<cmd.length;i++)
|
||||
cs = cs ^ cmd.charCodeAt(i);
|
||||
return cmd+"*"+cs.toString(16).toUpperCase().padStart(2, '0');
|
||||
}
|
||||
|
||||
// ===================================================
|
||||
|
||||
function jsFromBase64(b64) {
|
||||
var bin = atob(b64);
|
||||
var chunkSize = 128;
|
||||
var js = "\x10Bangle.setGPSPower(1);\n"; // turn GPS on
|
||||
//js += `\x10Bangle.on('GPS-raw',function (d) { if (d.startsWith("\\xB5\\x62\\x05\\x01")) Terminal.println("GPS ACK"); else if (d.startsWith("\\xB5\\x62\\x05\\x00")) Terminal.println("GPS NACK"); })\n`;
|
||||
//js += "\x10var t=getTime()+1;while(t>getTime());\n"; // wait 1 sec
|
||||
js += `\x10Serial1.write(atob("${btoa(String.fromCharCode.apply(null,UBX_MGA_INI_TIME_UTC()))}"))\n`; // set GPS time
|
||||
if (isB1) { // UBLOX
|
||||
//js += `\x10Bangle.on('GPS-raw',function (d) { if (d.startsWith("\\xB5\\x62\\x05\\x01")) Terminal.println("GPS ACK"); else if (d.startsWith("\\xB5\\x62\\x05\\x00")) Terminal.println("GPS NACK"); })\n`;
|
||||
//js += "\x10var t=getTime()+1;while(t>getTime());\n"; // wait 1 sec
|
||||
js += `\x10Serial1.write(atob("${btoa(String.fromCharCode.apply(null,UBX_MGA_INI_TIME_UTC()))}"))\n`; // set GPS time
|
||||
}
|
||||
if (isB2) { // CASIC
|
||||
|
||||
// Disable BDS, use just GPS (supposedly improve lock time)
|
||||
js += `\x10Serial1.println("${CASIC_CHECKSUM("$PCAS04,1")}")\n`; // set GPS-only mode
|
||||
// What about:
|
||||
// NAV-TIMEUTC (0x01 0x10)
|
||||
// NAV-PV (0x01 0x03)
|
||||
// or AGPS.zip uses AID-INI (0x0B 0x01)
|
||||
}
|
||||
|
||||
for (var i=0;i<bin.length;i+=chunkSize) {
|
||||
var chunk = bin.substr(i,chunkSize);
|
||||
|
|
@ -106,6 +143,15 @@
|
|||
oReq.send();
|
||||
}
|
||||
|
||||
// Called when we know what device we're using
|
||||
function onInit(device) {
|
||||
isB2 = (device && device.id=="BANGLEJS2");
|
||||
isB1 = !isB2;
|
||||
document.getElementById("banglejs1-info").style = isB1?"":"display:none";
|
||||
document.getElementById("banglejs2-info").style = isB2?"":"display:none";
|
||||
document.getElementById("upload-wrap").style = "";
|
||||
}
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
0.01: New App!
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
# BanglExercise
|
||||
|
||||
Can automatically track exercises while wearing the Bangle.js watch.
|
||||
|
||||
Currently only push ups and curls are supported.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This app is experimental but it seems to work quiet reliable for me.
|
||||
It could be and is likely that the threshold values for detecting exercises do not work for everyone.
|
||||
Therefore it would be great if we could improve this app together :-)
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
Select the exercise type you want to practice and go for it!
|
||||
Press stop to end your exercise.
|
||||
|
||||
|
||||
## Screenshots
|
||||

|
||||
|
||||
## TODO
|
||||
* Add other exercise types:
|
||||
* Rope jumps
|
||||
* Sit ups
|
||||
* ...
|
||||
* Save exercise summaries to file system
|
||||
* Configure daily goal for exercises
|
||||
* Find a nicer icon
|
||||
|
||||
|
||||
## Contribute
|
||||
Feel free to send in improvements and remarks.
|
||||
|
||||
## Creator
|
||||
Marco ([myxor](https://github.com/myxor))
|
||||
|
||||
## Icons
|
||||
Icons taken from [materialdesignicons](https://materialdesignicons.com) under Apache License 2.0
|
||||
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwIbYh/8AYM/+EP/wFBv4FB/4FB/4FHAwIEBAv4FPAgIGCAosHAofggYFD4EABgXgOgIFLDAQWBAo0BAoOAVIV/UYQABj/4AocDCwQFTg46CEY4vFAopBBApIAVA=="))
|
||||
|
|
@ -0,0 +1,362 @@
|
|||
const Layout = require("Layout");
|
||||
const heatshrink = require('heatshrink');
|
||||
const storage = require('Storage');
|
||||
|
||||
let tStart;
|
||||
let historyY = [];
|
||||
let historyZ = [];
|
||||
let historyAvgY = [];
|
||||
let historyAvgZ = [];
|
||||
let historySlopeY = [];
|
||||
let historySlopeZ = [];
|
||||
|
||||
let lastZeroPassCameFromPositive;
|
||||
let lastZeroPassTime = 0;
|
||||
|
||||
let lastExerciseCompletionTime = 0;
|
||||
let lastExerciseHalfCompletionTime = 0;
|
||||
|
||||
let exerciseType = {
|
||||
"id": "",
|
||||
"name": ""
|
||||
};
|
||||
|
||||
// add new exercises here:
|
||||
const exerciseTypes = [{
|
||||
"id": "pushup",
|
||||
"name": "push ups",
|
||||
"useYaxe": true,
|
||||
"useZaxe": false,
|
||||
"thresholdY": 2500,
|
||||
"thresholdMinTime": 1400, // mininmal time between two push ups in ms
|
||||
"thresholdMaxTime": 5000, // maximal time between two push ups in ms
|
||||
"thresholdMinDurationTime": 700, // mininmal duration of half a push ups in ms
|
||||
},
|
||||
{
|
||||
"id": "curl",
|
||||
"name": "curls",
|
||||
"useYaxe": true,
|
||||
"useZaxe": false,
|
||||
"thresholdY": 2500,
|
||||
"thresholdMinTime": 1000, // mininmal time between two curls in ms
|
||||
"thresholdMaxTime": 5000, // maximal time between two curls in ms
|
||||
"thresholdMinDurationTime": 500, // mininmal duration of half a push ups in ms
|
||||
}
|
||||
];
|
||||
let exerciseCounter = 0;
|
||||
|
||||
let layout;
|
||||
let recordActive = false;
|
||||
|
||||
// Size of average window for data analysis
|
||||
const avgSize = 6;
|
||||
|
||||
let hrtValue;
|
||||
|
||||
let settings = storage.readJSON("banglexercise.json", 1) || {
|
||||
'buzz': true
|
||||
};
|
||||
|
||||
function showMainMenu() {
|
||||
let menu;
|
||||
menu = {
|
||||
"": {
|
||||
title: "BanglExercise"
|
||||
}
|
||||
};
|
||||
|
||||
exerciseTypes.forEach(function(et) {
|
||||
menu["Do " + et.name] = function() {
|
||||
exerciseType = et;
|
||||
E.showMenu();
|
||||
startTraining();
|
||||
};
|
||||
});
|
||||
|
||||
if (exerciseCounter > 0) {
|
||||
menu["--------"] = {
|
||||
value: ""
|
||||
};
|
||||
menu["Last:"] = {
|
||||
value: exerciseCounter + " " + exerciseType.name
|
||||
};
|
||||
}
|
||||
menu.Exit = function() {
|
||||
load();
|
||||
};
|
||||
|
||||
E.showMenu(menu);
|
||||
}
|
||||
|
||||
function accelHandler(accel) {
|
||||
if (!exerciseType) return;
|
||||
const t = Math.round(new Date().getTime()); // time in ms
|
||||
const y = exerciseType.useYaxe ? accel.y * 8192 : 0;
|
||||
const z = exerciseType.useZaxe ? accel.z * 8192 : 0;
|
||||
//console.log(t, y, z);
|
||||
|
||||
if (exerciseType.useYaxe) {
|
||||
while (historyY.length > avgSize)
|
||||
historyY.shift();
|
||||
|
||||
historyY.push(y);
|
||||
|
||||
if (historyY.length > avgSize / 2) {
|
||||
const avgY = E.sum(historyY) / historyY.length;
|
||||
historyAvgY.push([t, avgY]);
|
||||
while (historyAvgY.length > avgSize)
|
||||
historyAvgY.shift();
|
||||
}
|
||||
}
|
||||
|
||||
if (exerciseType.useYaxe) {
|
||||
while (historyZ.length > avgSize)
|
||||
historyZ.shift();
|
||||
|
||||
historyZ.push(z);
|
||||
|
||||
if (historyZ.length > avgSize / 2) {
|
||||
const avgZ = E.sum(historyZ) / historyZ.length;
|
||||
historyAvgZ.push([t, avgZ]);
|
||||
while (historyAvgZ.length > avgSize)
|
||||
historyAvgZ.shift();
|
||||
}
|
||||
}
|
||||
|
||||
// slope for Y
|
||||
if (exerciseType.useYaxe) {
|
||||
let l = historyAvgY.length;
|
||||
if (l > 1) {
|
||||
const p1 = historyAvgY[l - 2];
|
||||
const p2 = historyAvgY[l - 1];
|
||||
const slopeY = (p2[1] - p1[1]) / (p2[0] / 1000 - p1[0] / 1000);
|
||||
// we use this data for exercises which can be detected by using Y axis data
|
||||
switch (exerciseType.id) {
|
||||
case "pushup":
|
||||
isValidYAxisExercise(slopeY, t);
|
||||
break;
|
||||
case "curl":
|
||||
isValidYAxisExercise(slopeY, t);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// slope for Z
|
||||
if (exerciseType.useZaxe) {
|
||||
l = historyAvgZ.length;
|
||||
if (l > 1) {
|
||||
const p1 = historyAvgZ[l - 2];
|
||||
const p2 = historyAvgZ[l - 1];
|
||||
const slopeZ = (p2[1] - p1[1]) / (p2[0] - p1[0]);
|
||||
historyAvgZ.shift();
|
||||
historySlopeZ.push([p2[0] - p1[0], slopeZ]);
|
||||
|
||||
// TODO: we can use this data for some exercises which can be detected by using Z axis data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if slope value of Y-axis data looks like an exercise
|
||||
*
|
||||
* In detail we look for slop values which are bigger than the configured Y threshold for the current exercise
|
||||
* Then we look for two consecutive slope values of which one is above 0 and the other is below zero.
|
||||
* If we find one pair of these values this could be part of one exercise.
|
||||
* Then we look for a pair of values which cross the zero from the otherwise direction
|
||||
*/
|
||||
function isValidYAxisExercise(slopeY, t) {
|
||||
if (!exerciseType) return;
|
||||
|
||||
const thresholdY = exerciseType.thresholdY;
|
||||
const thresholdMinTime = exerciseType.thresholdMinTime;
|
||||
const thresholdMaxTime = exerciseType.thresholdMaxTime;
|
||||
const thresholdMinDurationTime = exerciseType.thresholdMinDurationTime;
|
||||
const exerciseName = exerciseType.name;
|
||||
|
||||
if (Math.abs(slopeY) >= thresholdY) {
|
||||
historyAvgY.shift();
|
||||
historySlopeY.push([t, slopeY]);
|
||||
//console.log(t, Math.abs(slopeY));
|
||||
|
||||
const lSlopeY = historySlopeY.length;
|
||||
if (lSlopeY > 1) {
|
||||
const p1 = historySlopeY[lSlopeY - 1][1];
|
||||
const p2 = historySlopeY[lSlopeY - 2][1];
|
||||
if (p1 > 0 && p2 < 0) {
|
||||
if (lastZeroPassCameFromPositive == false) {
|
||||
lastExerciseHalfCompletionTime = t;
|
||||
//console.log(t, exerciseName + " half complete...");
|
||||
|
||||
layout.progress.label = "½";
|
||||
g.clear();
|
||||
layout.render();
|
||||
}
|
||||
|
||||
lastZeroPassCameFromPositive = true;
|
||||
lastZeroPassTime = t;
|
||||
}
|
||||
if (p2 > 0 && p1 < 0) {
|
||||
if (lastZeroPassCameFromPositive == true) {
|
||||
const tDiffLastExercise = t - lastExerciseCompletionTime;
|
||||
const tDiffStart = t - tStart;
|
||||
//console.log(t, exerciseName + " maybe complete?", Math.round(tDiffLastExercise), Math.round(tDiffStart));
|
||||
|
||||
// check minimal time between exercises:
|
||||
if ((lastExerciseCompletionTime <= 0 && tDiffStart >= thresholdMinTime) || tDiffLastExercise >= thresholdMinTime) {
|
||||
|
||||
// check maximal time between exercises:
|
||||
if (lastExerciseCompletionTime <= 0 || tDiffLastExercise <= thresholdMaxTime) {
|
||||
|
||||
// check minimal duration of exercise:
|
||||
const tDiffExerciseHalfCompletion = t - lastExerciseHalfCompletionTime;
|
||||
if (tDiffExerciseHalfCompletion > thresholdMinDurationTime) {
|
||||
//console.log(t, exerciseName + " complete!!!");
|
||||
|
||||
lastExerciseCompletionTime = t;
|
||||
exerciseCounter++;
|
||||
|
||||
layout.count.label = exerciseCounter;
|
||||
layout.progress.label = "";
|
||||
g.clear();
|
||||
layout.render();
|
||||
|
||||
if (settings.buzz)
|
||||
Bangle.buzz(100, 0.4);
|
||||
} else {
|
||||
//console.log(t, exerciseName + " to quick for duration time threshold!");
|
||||
lastExerciseCompletionTime = t;
|
||||
}
|
||||
} else {
|
||||
//console.log(t, exerciseName + " to slow for time threshold!");
|
||||
lastExerciseCompletionTime = t;
|
||||
}
|
||||
} else {
|
||||
//console.log(t, exerciseName + " to quick for time threshold!");
|
||||
lastExerciseCompletionTime = t;
|
||||
}
|
||||
}
|
||||
|
||||
lastZeroPassCameFromPositive = false;
|
||||
lastZeroPassTime = t;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function reset() {
|
||||
historyY = [];
|
||||
historyZ = [];
|
||||
historyAvgY = [];
|
||||
historyAvgZ = [];
|
||||
historySlopeY = [];
|
||||
historySlopeZ = [];
|
||||
|
||||
lastZeroPassCameFromPositive = undefined;
|
||||
lastZeroPassTime = 0;
|
||||
lastExerciseHalfCompletionTime = 0;
|
||||
lastExerciseCompletionTime = 0;
|
||||
exerciseCounter = 0;
|
||||
tStart = 0;
|
||||
}
|
||||
|
||||
|
||||
function startTraining() {
|
||||
if (recordActive) return;
|
||||
g.clear(1);
|
||||
reset();
|
||||
Bangle.setHRMPower(1, "banglexercise");
|
||||
if (!hrtValue) hrtValue = "...";
|
||||
|
||||
layout = new Layout({
|
||||
type: "v",
|
||||
c: [{
|
||||
type: "txt",
|
||||
id: "type",
|
||||
font: "6x8:2",
|
||||
label: exerciseType.name,
|
||||
pad: 5
|
||||
},
|
||||
{
|
||||
type: "h",
|
||||
c: [{
|
||||
type: "txt",
|
||||
id: "count",
|
||||
font: exerciseCounter < 100 ? "6x8:9" : "6x8:8",
|
||||
label: 10,
|
||||
pad: 5
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
id: "progress",
|
||||
font: "6x8:2",
|
||||
label: "",
|
||||
pad: 5
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "h",
|
||||
c: [{
|
||||
type: "img",
|
||||
pad: 4,
|
||||
src: function() {
|
||||
return heatshrink.decompress(atob("h0OwYOLkmQhMkgACByVJgESpIFBpEEBAIFBCgIFCCgsABwcAgQOCAAMSpAwDyBNM"));
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
id: "hrtRate",
|
||||
font: "6x8:2",
|
||||
label: hrtValue,
|
||||
pad: 5
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
type: "txt",
|
||||
id: "recording",
|
||||
font: "6x8:2",
|
||||
label: "TRAINING",
|
||||
bgCol: "#f00",
|
||||
pad: 5,
|
||||
fillx: 1
|
||||
},
|
||||
]
|
||||
}, {
|
||||
btns: [{
|
||||
label: "STOP",
|
||||
cb: () => {
|
||||
stopTraining();
|
||||
}
|
||||
}],
|
||||
lazy: false
|
||||
});
|
||||
layout.render();
|
||||
|
||||
Bangle.setPollInterval(80); // 12.5 Hz
|
||||
Bangle.on('accel', accelHandler);
|
||||
tStart = new Date().getTime();
|
||||
recordActive = true;
|
||||
if (settings.buzz)
|
||||
Bangle.buzz(200, 1);
|
||||
}
|
||||
|
||||
function stopTraining() {
|
||||
if (!recordActive) return;
|
||||
|
||||
g.clear(1);
|
||||
Bangle.removeListener('accel', accelHandler);
|
||||
Bangle.setHRMPower(0, "banglexercise");
|
||||
showMainMenu();
|
||||
recordActive = false;
|
||||
}
|
||||
|
||||
Bangle.on('HRM', function(hrm) {
|
||||
hrtValue = hrm.bpm;
|
||||
});
|
||||
|
||||
g.clear(1);
|
||||
showMainMenu();
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 690 B |
Binary file not shown.
|
After Width: | Height: | Size: 2.1 KiB |
|
|
@ -0,0 +1,21 @@
|
|||
(function(back) {
|
||||
const SETTINGS_FILE = "banglexercise.json";
|
||||
const storage = require('Storage');
|
||||
let settings = storage.readJSON(SETTINGS_FILE, 1) || {};
|
||||
function save(key, value) {
|
||||
settings[key] = value;
|
||||
storage.write(SETTINGS_FILE, settings);
|
||||
}
|
||||
E.showMenu({
|
||||
'': { 'title': 'BanglExercise' },
|
||||
'< Back': back,
|
||||
'Buzz': {
|
||||
value: "buzz" in settings ? settings.buzz : false,
|
||||
format: () => (settings.buzz ? 'Yes' : 'No'),
|
||||
onchange: () => {
|
||||
settings.buzz = !settings.buzz;
|
||||
save('buzz', settings.buzz);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
@ -7,5 +7,6 @@
|
|||
0.07: Added settings to adjust data that is shown for each row.
|
||||
0.08: Support for multiple screens. 24h graph for steps + HRM. Fullscreen Mode.
|
||||
0.09: Tab anywhere to open the launcher.
|
||||
0.10: Fix - Clock is unresponsive, if gadgetbridge connects.
|
||||
0.11: Added getting the gadgetbridge weather
|
||||
0.10: Removed swipes to be compatible with the Pattern Launcher. Stability improvements.
|
||||
0.11: Show the gadgetbridge weather temperature (settings).
|
||||
0.12: Added humidity to data.
|
||||
|
|
@ -4,20 +4,28 @@ A simple LCARS inspired clock.
|
|||
Note: To display the steps, the health app is required. If this app is not installed, the data will not be shown.
|
||||
To contribute you can open a PR at this [GitHub Repo]( https://github.com/peerdavid/BangleApps)
|
||||
|
||||
## Control
|
||||
* Tap left / right to change between screens.
|
||||
* Tap top / bottom to control the current screen.
|
||||
|
||||
## Features
|
||||
* LCARS Style watch face.
|
||||
* Full screen mode - widgets are still loaded.
|
||||
* Supports multiple screens with different data.
|
||||
* Tab anywhere to open the launcher.
|
||||
* [Screen 1] Date + Time + Lock status.
|
||||
* [Screen 1] Shows randomly images of real planets.
|
||||
* [Screen 1] Shows different states such as (charging, out of battery, GPS on etc.)
|
||||
* [Screen 1] Swipe up/down to activate an alarm.
|
||||
* [Screen 1] Shows 3 customizable datapoints on the first screen.
|
||||
* [Screen 1] The lower orange line indicates the battery level.
|
||||
* [Screen 2] Display graphs for steps + hrm on the second screen.
|
||||
* [Screen 2] Switch between day/month via swipe up/down.
|
||||
* Full screen mode - widgets are still loaded but not shown.
|
||||
* Tab on left/right to switch between different screens.
|
||||
* Cusomizable data that is shown on screen 1 (steps, weather etc.)
|
||||
* Shows random images of real planets.
|
||||
* Tap on top/bottom of screen 1 to activate an alarm.
|
||||
* The lower orange line indicates the battery level.
|
||||
* Display graphs for steps + hrm on the second screen.
|
||||
|
||||
## Data that can be configured
|
||||
* Steps - Steps loaded via the health module
|
||||
* Battery - Current battery level in %
|
||||
* VREF - Voltage of battery
|
||||
* HRM - Last measured HRM
|
||||
* Temp - Weather temperature loaded via the weather module + gadgetbridge
|
||||
* Humidity - Humidity loaded via the weather module + gadgetbridge
|
||||
* CoreT - Temperature of device
|
||||
|
||||
## Multiple screens support
|
||||
Access different screens via swipe left/ right
|
||||
|
|
@ -26,10 +34,7 @@ Access different screens via swipe left/ right
|
|||

|
||||
|
||||
|
||||
## Icons
|
||||
<div>Icons made by <a href="https://www.flaticon.com/authors/smashicons" title="Smashicons">Smashicons</a>, <a href="https://www.freepik.com" title="Freepik">Freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a></div>
|
||||
|
||||
|
||||
## Contributors
|
||||
- Creator: [David Peer](https://github.com/peerdavid).
|
||||
- Initial creation and improvements: [David Peer](https://github.com/peerdavid).
|
||||
- Improvements: [Adam Schmalhofer](https://github.com/adamschmalhofer).
|
||||
- Improvements: [Jon Warrington](https://github.com/BartokW).
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
const SETTINGS_FILE = "lcars.setting.json";
|
||||
const Storage = require("Storage");
|
||||
const weather = require('weather');
|
||||
|
||||
|
||||
// ...and overwrite them with any saved values
|
||||
// This way saved values are preserved if a new version adds more settings
|
||||
const locale = require('locale');
|
||||
const storage = require('Storage')
|
||||
let settings = {
|
||||
alarm: -1,
|
||||
dataRow1: "Battery",
|
||||
dataRow2: "Steps",
|
||||
dataRow3: "Temp."
|
||||
dataRow1: "Steps",
|
||||
dataRow2: "Temp",
|
||||
dataRow3: "Battery"
|
||||
};
|
||||
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||
for (const key in saved_settings) {
|
||||
|
|
@ -33,13 +28,13 @@ let cGrey = "#9E9E9E";
|
|||
let lcarsViewPos = 0;
|
||||
let drag;
|
||||
let hrmValue = 0;
|
||||
var plotWeek = false;
|
||||
var plotMonth = false;
|
||||
var disableInfoUpdate = true; // When gadgetbridge connects, step infos cannot be loaded
|
||||
|
||||
/*
|
||||
* Requirements and globals
|
||||
*/
|
||||
const locale = require('locale');
|
||||
|
||||
|
||||
var bgLeft = {
|
||||
width : 27, height : 176, bpp : 3,
|
||||
|
|
@ -123,37 +118,35 @@ function queueDraw() {
|
|||
|
||||
function printData(key, y, c){
|
||||
g.setFontAlign(-1,-1,0);
|
||||
var text = "ERR";
|
||||
var value = "NOT FOUND";
|
||||
key = key.toUpperCase()
|
||||
var text = key;
|
||||
var value = "ERR";
|
||||
|
||||
if(key == "Battery"){
|
||||
text = "BAT";
|
||||
value = E.getBattery() + "%";
|
||||
|
||||
} else if(key == "Steps"){
|
||||
if(key == "STEPS"){
|
||||
text = "STEP";
|
||||
value = getSteps();
|
||||
|
||||
} else if(key == "Temp."){
|
||||
text = "TEMP";
|
||||
value = Math.floor(E.getTemperature()) + "C";
|
||||
|
||||
} else if(key == "HRM"){
|
||||
text = "HRM";
|
||||
value = hrmValue;
|
||||
} else if(key == "BATTERY"){
|
||||
text = "BAT";
|
||||
value = E.getBattery() + "%";
|
||||
|
||||
} else if (key == "VREF"){
|
||||
text = "VREF";
|
||||
value = E.getAnalogVRef().toFixed(2) + "V";
|
||||
|
||||
} else if (key == "Weather"){
|
||||
text = "TEMP";
|
||||
const w = weather.get();
|
||||
if (!w) {
|
||||
value = "ERR";
|
||||
} else {
|
||||
value = require('locale').temp(w.temp-273.15); // applies conversion
|
||||
}
|
||||
} else if(key == "HRM"){
|
||||
value = hrmValue;
|
||||
|
||||
} else if (key == "TEMP"){
|
||||
var weather = getWeather();
|
||||
value = weather.temp;
|
||||
|
||||
} else if (key == "HUMIDITY"){
|
||||
text = "HUM";
|
||||
var weather = getWeather();
|
||||
value = parseInt(weather.hum) + "%";
|
||||
|
||||
} else if(key == "CORET"){
|
||||
value = locale.temp(parseInt(E.getTemperature()));
|
||||
}
|
||||
|
||||
g.setColor(c);
|
||||
|
|
@ -309,7 +302,7 @@ function drawPosition1(){
|
|||
}
|
||||
|
||||
// Plot HRM graph
|
||||
if(plotWeek){
|
||||
if(plotMonth){
|
||||
var data = new Uint16Array(32);
|
||||
var cnt = new Uint8Array(32);
|
||||
health.readDailySummaries(new Date(), h=>{
|
||||
|
|
@ -346,8 +339,8 @@ function drawPosition1(){
|
|||
g.setFontAlign(1, 1, 0);
|
||||
g.setFontAntonioMedium();
|
||||
g.setColor(cWhite);
|
||||
g.drawString("WEEK HRM", 154, 27);
|
||||
g.drawString("WEEK STEPS [K]", 154, 115);
|
||||
g.drawString("M-HRM", 154, 27);
|
||||
g.drawString("M-STEPS [K]", 154, 115);
|
||||
|
||||
// Plot day
|
||||
} else {
|
||||
|
|
@ -387,8 +380,8 @@ function drawPosition1(){
|
|||
g.setFontAlign(1, 1, 0);
|
||||
g.setFontAntonioMedium();
|
||||
g.setColor(cWhite);
|
||||
g.drawString("DAY HRM", 154, 27);
|
||||
g.drawString("DAY STEPS", 154, 115);
|
||||
g.drawString("D-HRM", 154, 27);
|
||||
g.drawString("D-STEPS", 154, 115);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -429,6 +422,32 @@ function getSteps() {
|
|||
}
|
||||
|
||||
|
||||
function getWeather(){
|
||||
var weather;
|
||||
|
||||
try {
|
||||
weather = require('weather').get();
|
||||
} catch(ex) {
|
||||
// Return default
|
||||
}
|
||||
|
||||
if (weather === undefined){
|
||||
weather = {
|
||||
temp: "-",
|
||||
hum: "-",
|
||||
txt: "-",
|
||||
wind: "-",
|
||||
wdir: "-",
|
||||
wrose: "-"
|
||||
};
|
||||
} else {
|
||||
weather.temp = locale.temp(parseInt(weather.temp-273.15))
|
||||
}
|
||||
|
||||
return weather;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Handle alarm
|
||||
*/
|
||||
|
|
@ -467,7 +486,7 @@ function handleAlarm(){
|
|||
.then(() => {
|
||||
// Update alarm state to disabled
|
||||
settings.alarm = -1;
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
storage.writeJSON(SETTINGS_FILE, settings);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -507,7 +526,7 @@ function increaseAlarm(){
|
|||
settings.alarm = getCurrentTimeInMinutes() + 5;
|
||||
}
|
||||
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
storage.writeJSON(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -518,7 +537,7 @@ function decreaseAlarm(){
|
|||
settings.alarm = -1;
|
||||
}
|
||||
|
||||
Storage.writeJSON(SETTINGS_FILE, settings);
|
||||
storage.writeJSON(SETTINGS_FILE, settings);
|
||||
}
|
||||
|
||||
function feedback(){
|
||||
|
|
@ -562,9 +581,9 @@ Bangle.on('touch', function(btn, e){
|
|||
drawState();
|
||||
return;
|
||||
}
|
||||
} else if (lcarsViewPos == 1 && (is_upper || is_lower) && plotWeek != is_lower){
|
||||
} else if (lcarsViewPos == 1 && (is_upper || is_lower) && plotMonth != is_lower){
|
||||
feedback();
|
||||
plotWeek = is_lower;
|
||||
plotMonth = is_lower;
|
||||
draw();
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
alarm: -1,
|
||||
dataRow1: "Battery",
|
||||
dataRow2: "Steps",
|
||||
dataRow3: "Temp."
|
||||
dataRow3: "Temp"
|
||||
};
|
||||
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
|
||||
for (const key in saved_settings) {
|
||||
|
|
@ -18,14 +18,14 @@
|
|||
storage.write(SETTINGS_FILE, settings)
|
||||
}
|
||||
|
||||
var data_options = ["Battery", "Steps", "Temp.", "HRM", "VREF", "Weather"];
|
||||
var data_options = ["Steps", "Battery", "VREF", "HRM", "Temp", "Humidity", "CoreT"];
|
||||
|
||||
E.showMenu({
|
||||
'': { 'title': 'LCARS Clock' },
|
||||
'< Back': back,
|
||||
'Row 1': {
|
||||
value: 0 | data_options.indexOf(settings.dataRow1),
|
||||
min: 0, max: 5,
|
||||
min: 0, max: 6,
|
||||
format: v => data_options[v],
|
||||
onchange: v => {
|
||||
settings.dataRow1 = data_options[v];
|
||||
|
|
@ -34,7 +34,7 @@
|
|||
},
|
||||
'Row 2': {
|
||||
value: 0 | data_options.indexOf(settings.dataRow2),
|
||||
min: 0, max: 5,
|
||||
min: 0, max: 6,
|
||||
format: v => data_options[v],
|
||||
onchange: v => {
|
||||
settings.dataRow2 = data_options[v];
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
},
|
||||
'Row 3': {
|
||||
value: 0 | data_options.indexOf(settings.dataRow3),
|
||||
min: 0, max: 5,
|
||||
min: 0, max: 6,
|
||||
format: v => data_options[v],
|
||||
onchange: v => {
|
||||
settings.dataRow3 = data_options[v];
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.6 KiB |
|
|
@ -27,3 +27,4 @@
|
|||
0.18: Use app-specific icon colors
|
||||
Spread message action buttons out
|
||||
Back button now goes back to list of messages
|
||||
If showMessage called with no message (eg all messages deleted) now return to the clock (fix #1267)
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ function showMessageSettings(msg) {
|
|||
|
||||
function showMessage(msgid) {
|
||||
var msg = MESSAGES.find(m=>m.id==msgid);
|
||||
if (!msg) return checkMessages({clockIfNoMsg:0,clockIfAllRead:0,showMsgIfUnread:0}); // go home if no message found
|
||||
if (!msg) return checkMessages({clockIfNoMsg:1,clockIfAllRead:0,showMsgIfUnread:0}); // go home if no message found
|
||||
if (msg.src=="Maps") {
|
||||
cancelReloadTimeout(); // don't auto-reload to clock now
|
||||
return showMapMessage(msg);
|
||||
|
|
|
|||
|
|
@ -43,3 +43,4 @@
|
|||
0.38: Restructed menus as per forum discussion
|
||||
0.39: Fix misbehaving debug info option
|
||||
0.40: Moved off into Utils, put System after Apps
|
||||
0.41: Stop users disabling all wake-up methods and locking themselves out (fix #1272)
|
||||
|
|
|
|||
|
|
@ -31,9 +31,12 @@ This is Bangle.js's settings menu
|
|||
* **LCD Brightness** set how bright the LCD is. Due to hardware limitations in the LCD backlight, you may notice flicker if the LCD is not at 100% brightness.
|
||||
* **LCD Timeout** how long should the LCD stay on for if no activity is detected. 0=stay on forever
|
||||
* **Wake on X** should the given activity wake up the Bangle.js LCD?
|
||||
* On Bangle.js 2 when locked the touchscreen is turned off to save power. Because of this,
|
||||
`Wake on Touch` actually uses the accelerometer, and you need to actually tap the display to wake Bangle.js.
|
||||
* **Twist X** these options adjust the sensitivity of `Wake on Twist` to ensure Bangle.js wakes up with just the right amount of wrist movement.
|
||||
|
||||
|
||||
|
||||
## Quiet Mode
|
||||
|
||||
Quiet Mode is a hint to apps and widgets that you do not want to be disturbed.
|
||||
|
|
|
|||
|
|
@ -11,8 +11,18 @@ function updateSettings() {
|
|||
}
|
||||
|
||||
function updateOptions() {
|
||||
var o = settings.options;
|
||||
// Check to make sure nobody disabled all wakeups and locked themselves out!
|
||||
if (BANGLEJS2) {
|
||||
if (!(o.wakeOnBTN1||o.wakeOnFaceUp||o.wakeOnTouch||o.wakeOnTwist)) {
|
||||
o.wakeOnBTN1 = true;
|
||||
}
|
||||
} else {
|
||||
if (!(o.wakeOnBTN1||o.wakeOnBTN2||o.wakeOnBTN3||o.wakeOnFaceUp||o.wakeOnTouch||o.wakeOnTwist))
|
||||
o.wakeOnBTN2 = true;
|
||||
}
|
||||
updateSettings();
|
||||
Bangle.setOptions(settings.options)
|
||||
Bangle.setOptions(o)
|
||||
}
|
||||
|
||||
function gToInternal(g) {
|
||||
|
|
@ -63,7 +73,7 @@ const boolFormat = v => v ? /*LANG*/"On" : /*LANG*/"Off";
|
|||
function showMainMenu() {
|
||||
|
||||
const mainmenu = {
|
||||
'': { 'title': 'Settings' },
|
||||
'': { 'title': /*LANG*/'Settings' },
|
||||
'< Back': ()=>load(),
|
||||
/*LANG*/'Apps': ()=>showAppSettingsMenu(),
|
||||
/*LANG*/'System': ()=>showSystemMenu(),
|
||||
|
|
@ -78,7 +88,7 @@ function showMainMenu() {
|
|||
function showSystemMenu() {
|
||||
|
||||
const mainmenu = {
|
||||
'': { 'title': 'System' },
|
||||
'': { 'title': /*LANG*/'System' },
|
||||
'< Back': ()=>showMainMenu(),
|
||||
/*LANG*/'Theme': ()=>showThemeMenu(),
|
||||
/*LANG*/'LCD': ()=>showLCDMenu(),
|
||||
|
|
@ -122,7 +132,7 @@ function showAlertsMenu() {
|
|||
}
|
||||
|
||||
const mainmenu = {
|
||||
'': { 'title': 'Alerts' },
|
||||
'': { 'title': /*LANG*/'Alerts' },
|
||||
'< Back': ()=>showMainMenu(),
|
||||
/*LANG*/'Beep': beepMenuItem,
|
||||
/*LANG*/'Vibration': {
|
||||
|
|
@ -159,8 +169,8 @@ function showBLEMenu() {
|
|||
E.showMenu({
|
||||
'': { 'title': 'Bluetooth' },
|
||||
'< Back': ()=>showMainMenu(),
|
||||
'Make Connectable': ()=>makeConnectable(),
|
||||
'BLE': {
|
||||
/*LANG*/'Make Connectable': ()=>makeConnectable(),
|
||||
/*LANG*/'BLE': {
|
||||
value: settings.ble,
|
||||
format: boolFormat,
|
||||
onchange: () => {
|
||||
|
|
@ -168,7 +178,7 @@ function showBLEMenu() {
|
|||
updateSettings();
|
||||
}
|
||||
},
|
||||
'Programmable': {
|
||||
/*LANG*/'Programmable': {
|
||||
value: settings.blerepl,
|
||||
format: boolFormat,
|
||||
onchange: () => {
|
||||
|
|
@ -176,7 +186,7 @@ function showBLEMenu() {
|
|||
updateSettings();
|
||||
}
|
||||
},
|
||||
'HID': {
|
||||
/*LANG*/'HID': {
|
||||
value: Math.max(0,0 | hidV.indexOf(settings.HID)),
|
||||
min: 0, max: 3,
|
||||
format: v => hidN[v],
|
||||
|
|
@ -185,11 +195,11 @@ function showBLEMenu() {
|
|||
updateSettings();
|
||||
}
|
||||
},
|
||||
'Passkey BETA': {
|
||||
/*LANG*/'Passkey BETA': {
|
||||
value: settings.passkey?settings.passkey:"none",
|
||||
onchange: () => setTimeout(showPasskeyMenu) // graphical_menu redraws after the call
|
||||
},
|
||||
'Whitelist': {
|
||||
/*LANG*/'Whitelist': {
|
||||
value: settings.whitelist?(settings.whitelist.length+" devs"):"off",
|
||||
onchange: () => setTimeout(showWhitelistMenu) // graphical_menu redraws after the call
|
||||
}
|
||||
|
|
@ -213,7 +223,7 @@ function showThemeMenu() {
|
|||
var m = E.showMenu({
|
||||
'':{title:'Theme'},
|
||||
'< Back': ()=>showSystemMenu(),
|
||||
'Dark BW': ()=>{
|
||||
/*LANG*/'Dark BW': ()=>{
|
||||
upd({
|
||||
fg:cl("#fff"), bg:cl("#000"),
|
||||
fg2:cl("#0ff"), bg2:cl("#000"),
|
||||
|
|
@ -221,7 +231,7 @@ function showThemeMenu() {
|
|||
dark:true
|
||||
});
|
||||
},
|
||||
'Light BW': ()=>{
|
||||
/*LANG*/'Light BW': ()=>{
|
||||
upd({
|
||||
fg:cl("#000"), bg:cl("#fff"),
|
||||
fg2:cl("#000"), bg2:cl("#cff"),
|
||||
|
|
@ -229,7 +239,7 @@ function showThemeMenu() {
|
|||
dark:false
|
||||
});
|
||||
},
|
||||
'Customize': ()=>showCustomThemeMenu(),
|
||||
/*LANG*/'Customize': ()=>showCustomThemeMenu(),
|
||||
});
|
||||
|
||||
function showCustomThemeMenu() {
|
||||
|
|
@ -261,9 +271,9 @@ function showThemeMenu() {
|
|||
"< Back": () => showThemeMenu()
|
||||
};
|
||||
const labels = {
|
||||
fg: 'Foreground', bg: 'Background',
|
||||
fg2: 'Foreground 2', bg2: 'Background 2',
|
||||
fgH: 'Highlight FG', bgH: 'Highlight BG',
|
||||
fg: /*LANG*/'Foreground', bg: /*LANG*/'Background',
|
||||
fg2: /*LANG*/'Foreground 2', bg2: /*LANG*/'Background 2',
|
||||
fgH: /*LANG*/'Highlight FG', bgH: /*LANG*/'Highlight BG',
|
||||
};
|
||||
["fg", "bg", "fg2", "bg2", "fgH", "bgH"].forEach(t => {
|
||||
menu[labels[t]] = {
|
||||
|
|
@ -292,7 +302,7 @@ function showThemeMenu() {
|
|||
function showPasskeyMenu() {
|
||||
var menu = {
|
||||
"< Back" : ()=>showBLEMenu(),
|
||||
"Disable" : () => {
|
||||
/*LANG*/"Disable" : () => {
|
||||
settings.passkey = undefined;
|
||||
updateSettings();
|
||||
showBLEMenu();
|
||||
|
|
@ -320,7 +330,7 @@ function showPasskeyMenu() {
|
|||
function showWhitelistMenu() {
|
||||
var menu = {
|
||||
"< Back" : ()=>showBLEMenu(),
|
||||
"Disable" : () => {
|
||||
/*LANG*/"Disable" : () => {
|
||||
settings.whitelist = undefined;
|
||||
updateSettings();
|
||||
showBLEMenu();
|
||||
|
|
@ -328,7 +338,7 @@ function showWhitelistMenu() {
|
|||
};
|
||||
if (settings.whitelist) settings.whitelist.forEach(function(d){
|
||||
menu[d.substr(0,17)] = function() {
|
||||
E.showPrompt('Remove\n'+d).then((v) => {
|
||||
E.showPrompt(/*LANG*/'Remove\n'+d).then((v) => {
|
||||
if (v) {
|
||||
settings.whitelist.splice(settings.whitelist.indexOf(d),1);
|
||||
updateSettings();
|
||||
|
|
@ -337,8 +347,8 @@ function showWhitelistMenu() {
|
|||
});
|
||||
}
|
||||
});
|
||||
menu['Add Device']=function() {
|
||||
E.showAlert("Connect device\nto add to\nwhitelist","Whitelist").then(function() {
|
||||
menu[/*LANG*/'Add Device']=function() {
|
||||
E.showAlert(/*LANG*/"Connect device\nto add to\nwhitelist",/*LANG*/"Whitelist").then(function() {
|
||||
NRF.removeAllListeners('connect');
|
||||
showWhitelistMenu();
|
||||
});
|
||||
|
|
@ -358,7 +368,7 @@ function showLCDMenu() {
|
|||
const lcdMenu = {
|
||||
'': { 'title': 'LCD' },
|
||||
'< Back': ()=>showSystemMenu(),
|
||||
'LCD Brightness': {
|
||||
/*LANG*/'LCD Brightness': {
|
||||
value: settings.brightness,
|
||||
min: 0.1,
|
||||
max: 1,
|
||||
|
|
@ -369,7 +379,7 @@ function showLCDMenu() {
|
|||
Bangle.setLCDBrightness(settings.brightness);
|
||||
}
|
||||
},
|
||||
'LCD Timeout': {
|
||||
/*LANG*/'LCD Timeout': {
|
||||
value: settings.timeout,
|
||||
min: 0,
|
||||
max: 60,
|
||||
|
|
@ -380,7 +390,7 @@ function showLCDMenu() {
|
|||
Bangle.setLCDTimeout(settings.timeout);
|
||||
}
|
||||
},
|
||||
'Wake on BTN1': {
|
||||
/*LANG*/'Wake on BTN1': {
|
||||
value: settings.options.wakeOnBTN1,
|
||||
format: boolFormat,
|
||||
onchange: () => {
|
||||
|
|
@ -391,7 +401,7 @@ function showLCDMenu() {
|
|||
};
|
||||
if (!BANGLEJS2)
|
||||
Object.assign(lcdMenu, {
|
||||
'Wake on BTN2': {
|
||||
/*LANG*/'Wake on BTN2': {
|
||||
value: settings.options.wakeOnBTN2,
|
||||
format: boolFormat,
|
||||
onchange: () => {
|
||||
|
|
@ -399,7 +409,7 @@ function showLCDMenu() {
|
|||
updateOptions();
|
||||
}
|
||||
},
|
||||
'Wake on BTN3': {
|
||||
/*LANG*/'Wake on BTN3': {
|
||||
value: settings.options.wakeOnBTN3,
|
||||
format: boolFormat,
|
||||
onchange: () => {
|
||||
|
|
@ -408,7 +418,7 @@ function showLCDMenu() {
|
|||
}
|
||||
}});
|
||||
Object.assign(lcdMenu, {
|
||||
'Wake on FaceUp': {
|
||||
/*LANG*/'Wake on FaceUp': {
|
||||
value: settings.options.wakeOnFaceUp,
|
||||
format: boolFormat,
|
||||
onchange: () => {
|
||||
|
|
@ -416,7 +426,7 @@ function showLCDMenu() {
|
|||
updateOptions();
|
||||
}
|
||||
},
|
||||
'Wake on Touch': {
|
||||
/*LANG*/'Wake on Touch': {
|
||||
value: settings.options.wakeOnTouch,
|
||||
format: boolFormat,
|
||||
onchange: () => {
|
||||
|
|
@ -424,7 +434,7 @@ function showLCDMenu() {
|
|||
updateOptions();
|
||||
}
|
||||
},
|
||||
'Wake on Twist': {
|
||||
/*LANG*/'Wake on Twist': {
|
||||
value: settings.options.wakeOnTwist,
|
||||
format: boolFormat,
|
||||
onchange: () => {
|
||||
|
|
@ -432,7 +442,7 @@ function showLCDMenu() {
|
|||
updateOptions();
|
||||
}
|
||||
},
|
||||
'Twist Threshold': {
|
||||
/*LANG*/'Twist Threshold': {
|
||||
value: internalToG(settings.options.twistThreshold),
|
||||
min: -0.5,
|
||||
max: 0.5,
|
||||
|
|
@ -442,7 +452,7 @@ function showLCDMenu() {
|
|||
updateOptions();
|
||||
}
|
||||
},
|
||||
'Twist Max Y': {
|
||||
/*LANG*/'Twist Max Y': {
|
||||
value: settings.options.twistMaxY,
|
||||
min: -1500,
|
||||
max: 1500,
|
||||
|
|
@ -452,7 +462,7 @@ function showLCDMenu() {
|
|||
updateOptions();
|
||||
}
|
||||
},
|
||||
'Twist Timeout': {
|
||||
/*LANG*/'Twist Timeout': {
|
||||
value: settings.options.twistTimeout,
|
||||
min: 0,
|
||||
max: 2000,
|
||||
|
|
@ -468,9 +478,9 @@ function showLCDMenu() {
|
|||
|
||||
function showLocaleMenu() {
|
||||
const localemenu = {
|
||||
'': { 'title': 'Locale' },
|
||||
'': { 'title': /*LANG*/'Locale' },
|
||||
'< Back': ()=>showSystemMenu(),
|
||||
'Time Zone': {
|
||||
/*LANG*/'Time Zone': {
|
||||
value: settings.timezone,
|
||||
min: -11,
|
||||
max: 13,
|
||||
|
|
@ -480,7 +490,7 @@ function showLocaleMenu() {
|
|||
updateSettings();
|
||||
}
|
||||
},
|
||||
'Clock Style': {
|
||||
/*LANG*/'Clock Style': {
|
||||
value: !!settings["12hour"],
|
||||
format: v => v ? "12hr" : "24hr",
|
||||
onchange: v => {
|
||||
|
|
@ -494,29 +504,29 @@ function showLocaleMenu() {
|
|||
|
||||
function showUtilMenu() {
|
||||
var menu = {
|
||||
'': { 'title': 'Utilities' },
|
||||
'': { 'title': /*LANG*/'Utilities' },
|
||||
'< Back': ()=>showMainMenu(),
|
||||
'Debug Info': {
|
||||
/*LANG*/'Debug Info': {
|
||||
value: E.clip(0|settings.log,0,2),
|
||||
min: 0,
|
||||
max: 2,
|
||||
format: v => ["Hide","Show","Log"][E.clip(0|v,0,2)],
|
||||
format: v => [/*LANG*/"Hide",/*LANG*/"Show",/*LANG*/"Log"][E.clip(0|v,0,2)],
|
||||
onchange: v => {
|
||||
settings.log = v;
|
||||
updateSettings();
|
||||
}
|
||||
},
|
||||
'Compact Storage': () => {
|
||||
E.showMessage("Compacting...\nTakes approx\n1 minute",{title:"Storage"});
|
||||
/*LANG*/'Compact Storage': () => {
|
||||
E.showMessage(/*LANG*/"Compacting...\nTakes approx\n1 minute",{title:/*LANG*/"Storage"});
|
||||
require("Storage").compact();
|
||||
showUtilMenu();
|
||||
},
|
||||
'Rewrite Settings': () => {
|
||||
/*LANG*/'Rewrite Settings': () => {
|
||||
require("Storage").write(".boot0","eval(require('Storage').read('bootupdate.js'));");
|
||||
load("setting.app.js");
|
||||
},
|
||||
'Flatten Battery': () => {
|
||||
E.showMessage('Flattening battery - this can take hours.\nLong-press button to cancel.');
|
||||
/*LANG*/'Flatten Battery': () => {
|
||||
E.showMessage(/*LANG*/'Flattening battery - this can take hours.\nLong-press button to cancel.');
|
||||
Bangle.setLCDTimeout(0);
|
||||
Bangle.setLCDPower(1);
|
||||
if (Bangle.setGPSPower) Bangle.setGPSPower(1,"flat");
|
||||
|
|
@ -528,8 +538,8 @@ function showUtilMenu() {
|
|||
var i=1000;while (i--);
|
||||
}, 1);
|
||||
},
|
||||
'Reset Settings': () => {
|
||||
E.showPrompt('Reset to Defaults?',{title:"Settings"}).then((v) => {
|
||||
/*LANG*/'Reset Settings': () => {
|
||||
E.showPrompt(/*LANG*/'Reset to Defaults?',{title:/*LANG*/"Settings"}).then((v) => {
|
||||
if (v) {
|
||||
E.showMessage('Resetting');
|
||||
resetSettings();
|
||||
|
|
@ -540,8 +550,8 @@ function showUtilMenu() {
|
|||
/*LANG*/'Turn Off': ()=>{ if (Bangle.softOff) Bangle.softOff(); else Bangle.off() }
|
||||
};
|
||||
if (Bangle.factoryReset) {
|
||||
menu['Factory Reset'] = ()=>{
|
||||
E.showPrompt('This will remove everything!',{title:"Factory Reset"}).then((v) => {
|
||||
menu[/*LANG*/'Factory Reset'] = ()=>{
|
||||
E.showPrompt(/*LANG*/'This will remove everything!',{title:/*LANG*/"Factory Reset"}).then((v) => {
|
||||
if (v) {
|
||||
E.showMessage();
|
||||
Terminal.setConsole();
|
||||
|
|
@ -558,7 +568,7 @@ function makeConnectable() {
|
|||
try { NRF.wake(); } catch (e) { }
|
||||
Bluetooth.setConsole(1);
|
||||
var name = "Bangle.js " + NRF.getAddress().substr(-5).replace(":", "");
|
||||
E.showPrompt(name + "\nStay Connectable?", { title: "Connectable" }).then(r => {
|
||||
E.showPrompt(name + /*LANG*/"\nStay Connectable?", { title: /*LANG*/"Connectable" }).then(r => {
|
||||
if (settings.ble != r) {
|
||||
settings.ble = r;
|
||||
updateSettings();
|
||||
|
|
@ -574,7 +584,7 @@ function showClockMenu() {
|
|||
.sort((a, b) => a.sortorder - b.sortorder);
|
||||
const clockMenu = {
|
||||
'': {
|
||||
'title': 'Select Clock',
|
||||
'title': /*LANG*/'Select Clock',
|
||||
},
|
||||
'< Back': ()=>showSystemMenu(),
|
||||
};
|
||||
|
|
@ -592,7 +602,7 @@ function showClockMenu() {
|
|||
};
|
||||
});
|
||||
if (clockApps.length === 0) {
|
||||
clockMenu["No Clocks Found"] = () => { };
|
||||
clockMenu[/*LANG*/"No Clocks Found"] = () => { };
|
||||
}
|
||||
return E.showMenu(clockMenu);
|
||||
}
|
||||
|
|
@ -600,47 +610,47 @@ function showClockMenu() {
|
|||
function showSetTimeMenu() {
|
||||
d = new Date();
|
||||
const timemenu = {
|
||||
'': { 'title': 'Set Time' },
|
||||
'': { 'title': /*LANG*/'Set Time' },
|
||||
'< Back': function () {
|
||||
setTime(d.getTime() / 1000);
|
||||
showSystemMenu();
|
||||
},
|
||||
'Hour': {
|
||||
/*LANG*/'Hour': {
|
||||
value: d.getHours(),
|
||||
onchange: function (v) {
|
||||
this.value = (v+24)%24;
|
||||
d.setHours(this.value);
|
||||
}
|
||||
},
|
||||
'Minute': {
|
||||
/*LANG*/'Minute': {
|
||||
value: d.getMinutes(),
|
||||
onchange: function (v) {
|
||||
this.value = (v+60)%60;
|
||||
d.setMinutes(this.value);
|
||||
}
|
||||
},
|
||||
'Second': {
|
||||
/*LANG*/'Second': {
|
||||
value: d.getSeconds(),
|
||||
onchange: function (v) {
|
||||
this.value = (v+60)%60;
|
||||
d.setSeconds(this.value);
|
||||
}
|
||||
},
|
||||
'Date': {
|
||||
/*LANG*/'Date': {
|
||||
value: d.getDate(),
|
||||
onchange: function (v) {
|
||||
this.value = ((v+30)%31)+1;
|
||||
d.setDate(this.value);
|
||||
}
|
||||
},
|
||||
'Month': {
|
||||
/*LANG*/'Month': {
|
||||
value: d.getMonth() + 1,
|
||||
onchange: function (v) {
|
||||
this.value = ((v+11)%12)+1;
|
||||
d.setMonth(this.value - 1);
|
||||
}
|
||||
},
|
||||
'Year': {
|
||||
/*LANG*/'Year': {
|
||||
value: d.getFullYear(),
|
||||
min: 2019,
|
||||
max: 2100,
|
||||
|
|
@ -654,7 +664,7 @@ function showSetTimeMenu() {
|
|||
|
||||
function showAppSettingsMenu() {
|
||||
let appmenu = {
|
||||
'': { 'title': 'App Settings' },
|
||||
'': { 'title': /*LANG*/'App Settings' },
|
||||
'< Back': ()=>showMainMenu(),
|
||||
}
|
||||
const apps = storage.list(/\.settings\.js$/)
|
||||
|
|
@ -671,7 +681,7 @@ function showAppSettingsMenu() {
|
|||
return 0;
|
||||
})
|
||||
if (apps.length === 0) {
|
||||
appmenu['No app has settings'] = () => { };
|
||||
appmenu[/*LANG*/'No app has settings'] = () => { };
|
||||
}
|
||||
apps.forEach(function (app) {
|
||||
appmenu[app.name] = () => { showAppSettings(app) };
|
||||
|
|
@ -688,17 +698,17 @@ function showAppSettings(app) {
|
|||
appSettings = eval(appSettings);
|
||||
} catch (e) {
|
||||
console.log(`${app.name} settings error:`, e)
|
||||
return showError('Error in settings');
|
||||
return showError(/*LANG*/'Error in settings');
|
||||
}
|
||||
if (typeof appSettings !== "function") {
|
||||
return showError('Invalid settings');
|
||||
return showError(/*LANG*/'Invalid settings');
|
||||
}
|
||||
try {
|
||||
// pass showAppSettingsMenu as "back" argument
|
||||
appSettings(()=>showAppSettingsMenu());
|
||||
} catch (e) {
|
||||
console.log(`${app.name} settings error:`, e)
|
||||
return showError('Error in settings');
|
||||
return showError(/*LANG*/'Error in settings');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ const APP_KEYS = [
|
|||
];
|
||||
const STORAGE_KEYS = ['name', 'url', 'content', 'evaluate', 'noOverwite', 'supports'];
|
||||
const DATA_KEYS = ['name', 'wildcard', 'storageFile', 'url', 'content', 'evaluate'];
|
||||
const SUPPORTS_DEVICES = ["BANGLEJS","BANGLEJS2"]; // device IDs allowed for 'supports'
|
||||
const FORBIDDEN_FILE_NAME_CHARS = /[,;]/; // used as separators in appid.info
|
||||
const VALID_DUPLICATES = [ '.tfmodel', '.tfnames' ];
|
||||
const GRANDFATHERED_ICONS = ["s7clk", "snek", "astral", "alpinenav", "slomoclock", "arrow", "pebble", "rebble"];
|
||||
|
|
@ -90,7 +91,7 @@ apps.forEach((app,appIdx) => {
|
|||
if (!Array.isArray(app.supports)) ERROR(`App ${app.id} has no 'supports' field or it's not an array`);
|
||||
else {
|
||||
app.supports.forEach(dev => {
|
||||
if (!["BANGLEJS","BANGLEJS2"].includes(dev))
|
||||
if (!SUPPORTS_DEVICES.includes(dev))
|
||||
ERROR(`App ${app.id} has unknown device in 'supports' field - ${dev}`);
|
||||
});
|
||||
}
|
||||
|
|
@ -140,6 +141,13 @@ apps.forEach((app,appIdx) => {
|
|||
if (char) ERROR(`App ${app.id} storage file ${file.name} contains invalid character "${char[0]}"`)
|
||||
if (fileNames.includes(file.name) && !file.supports) // assume that there aren't duplicates if 'supports' is set
|
||||
ERROR(`App ${app.id} file ${file.name} is a duplicate`);
|
||||
if (file.supports && !Array.isArray(file.supports))
|
||||
ERROR(`App ${app.id} file ${file.name} supports field must be an array`);
|
||||
if (file.supports)
|
||||
file.supports.forEach(dev => {
|
||||
if (!SUPPORTS_DEVICES.includes(dev))
|
||||
ERROR(`App ${app.id} file ${file.name} has unknown device in 'supports' field - ${dev}`);
|
||||
});
|
||||
fileNames.push(file.name);
|
||||
allFiles.push({app: app.id, file: file.name});
|
||||
if (file.url) if (!fs.existsSync(appDir+file.url)) ERROR(`App ${app.id} file ${file.url} doesn't exist`);
|
||||
|
|
@ -271,7 +279,8 @@ while(fileA=allFiles.pop()) {
|
|||
if (globA.test(nameB)||globB.test(nameA)) {
|
||||
if (isGlob(nameA)||isGlob(nameB))
|
||||
ERROR(`App ${fileB.app} ${typeB} file ${nameB} matches app ${fileA.app} ${typeB} file ${nameA}`)
|
||||
else WARN(`App ${fileB.app} ${typeB} file ${nameB} is also listed as ${typeA} file for app ${fileA.app}`)
|
||||
else if (fileA.app != fileB.app)
|
||||
WARN(`App ${fileB.app} ${typeB} file ${nameB} is also listed as ${typeA} file for app ${fileA.app}`)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue