1
0
Fork 0

Merge branch 'espruino:master' into master

master
wagnerf42 2023-07-07 10:20:02 +02:00 committed by GitHub
commit 897838a096
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
173 changed files with 4950 additions and 1016 deletions

View File

@ -14,5 +14,6 @@
{"name":"90sclk.app.js","url":"app.js"},
{"name":"90sclk.img","url":"app-icon.js","evaluate":true},
{"name":"90sclk.settings.js","url":"settings.js"}
]
],
"data": [{"name":"90sclk.setting.json"}]
}

View File

@ -14,5 +14,6 @@
{"name":"activepedom.settings.js","url":"settings.js"},
{"name":"activepedom.img","url":"app-icon.js","evaluate":true},
{"name":"activepedom.app.js","url":"app.js"}
]
],
"data":[{"name":"activepedom.settings.json"}]
}

View File

@ -13,3 +13,4 @@
Added dynamic, short and range fields to clkinfo
0.12: Added color field and updating clkinfo periodically (running events)
0.13: Show day of the week in date
0.14: Fixed "Today" and "Yesterday" wrongly displayed for allDay events on some time zones

View File

@ -38,13 +38,12 @@ function formatDay(date) {
if (!settings.useToday) {
return formattedDate;
}
const dateformatted = date.toISOString().split('T')[0]; // yyyy-mm-dd
const today = new Date(Date.now()).toISOString().split('T')[0]; // yyyy-mm-dd
if (dateformatted == today) {
const today = new Date(Date.now());
if (date.getDay() == today.getDay() && date.getMonth() == today.getMonth())
return /*LANG*/"Today ";
} else {
const tomorrow = new Date(Date.now() + 86400 * 1000).toISOString().split('T')[0]; // yyyy-mm-dd
if (dateformatted == tomorrow) {
else {
const tomorrow = new Date(Date.now() + 86400 * 1000);
if (date.getDay() == tomorrow.getDay() && date.getMonth() == tomorrow.getMonth()) {
return /*LANG*/"Tomorrow ";
}
return formattedDate;

View File

@ -1,7 +1,7 @@
{
"id": "agenda",
"name": "Agenda",
"version": "0.13",
"version": "0.14",
"description": "Simple agenda",
"icon": "agenda.png",
"screenshots": [{"url":"screenshot_agenda_overview.png"}, {"url":"screenshot_agenda_event1.png"}, {"url":"screenshot_agenda_event2.png"}],

1
apps/alyxclock/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: first release

4
apps/alyxclock/README.md Normal file
View File

@ -0,0 +1,4 @@
# Half-Life Alyx Style clock
![](screenshot_alyxclock.png)

View File

@ -0,0 +1,174 @@
const icoH = [
[0,1,1,0,0,1,1,0],
[1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1],
[1,1,1,1,1,1,1,1],
[0,1,1,1,1,1,1,0],
[0,0,1,1,1,1,0,0],
[0,0,0,1,1,0,0,0],
[0,0,0,0,0,0,0,0],
]
const icoR = [
[0,0,0,0,1,1,1,1,0,0,0,0],
[0,0,1,1,0,0,0,0,1,1,0,0],
[0,1,1,1,1,0,0,1,1,0,1,0],
[0,1,1,0,0,0,0,0,0,0,1,0],
[1,1,1,1,1,1,1,1,0,0,0,1],
[1,1,0,0,1,0,0,0,0,0,0,1],
[1,1,1,1,1,1,1,0,1,1,0,1],
[1,1,1,1,1,1,0,0,0,0,1,1],
[0,1,1,1,1,1,1,1,1,1,1,0],
[0,1,1,1,1,1,1,1,1,1,1,0],
[0,0,1,1,1,1,1,1,1,1,0,0],
[0,0,0,0,1,1,1,1,0,0,0,0],
]
let idTimeout = null;
function icon (icon, x, y, size, gap) {
const color = g.getColor();
for (let r=0; r<icon.length; r++) {
for (let c=0; c<icon[r].length; c++) {
if (icon[r][c]===1){
g.setColor(color);
g.fillRect(c * size + x, r * size + y, (c+1) * size - gap + x, (r+1)*size - gap + y);
g.setColor('#fff');
g.drawLine(c * size + x + size/2 - 1, r * size + y + size/2 - 1, c * size + x + size/2 - 1, r * size + y + size/2 - 1, )
}
}
}
g.setColor(color);
}
function ohmA(x, y) {
g.setColor('#666');
g.fillRect(x, y, x+8, y+15);
g.setColor('#00f');
g.drawLine(x, y + 4, x + 8, y + 4);
g.setColor('#f00');
g.drawLine(x, y + 6, x + 8, y + 6);
g.setColor('#0f0');
g.drawLine(x, y + 8, x + 8, y + 8);
}
function ohmB(x, y) {
g.setColor('#666');
g.fillRect(x, y, x+15, y+8);
g.setColor('#00f');
g.drawLine(x + 4, y + 8, x + 4, y);
g.setColor('#f00');
g.drawLine(x + 6, y + 8, x + 6, y);
g.setColor('#0f0');
g.drawLine(x + 8, y + 8, x + 8, y);
}
function heart (x, y) {
g.setColor('#000');
g.fillRect(x-2, y-2, x + 32, y + 32)
g.setColor('#666');
g.drawRect(x-2, y-2, x + 32, y + 32)
g.setColor('#f00');
icon(icoH, x, y, 4, 2);
}
function resin() {
let d = Date();
let h = d.getHours();
let m = d.getMinutes();
const resinPosX = 25;
const resinPosY = 130;
g.setColor('#000');
g.fillRect(resinPosX - 3, resinPosY - 3, Bangle.appRect.w - resinPosX + 2, resinPosY + 40);
g.setColor('#666');
g.drawRect(resinPosX - 3, resinPosY - 3, Bangle.appRect.w - resinPosX + 2, resinPosY + 40);
g.setColor('#6ff');
icon(icoR, resinPosX, resinPosY, 3, 1);
g.setFont('6x8', 5);
g.setFontAlign(-1, -1);
g.drawString('_' + (m<10?'0':'')+m, resinPosX + 40, resinPosY - 5);
g.setFontAlign(1, -1);
g.setFont('6x8', 2);
g.drawString(h, resinPosX + 66, resinPosY);
}
function screw(x, y) {
g.setColor('#666').fillCircle(x, y, 4).setColor('#000').drawLine(x - 4, y, x + 4, y)
}
function led(x,y) {
g.setColor('#0f0').fillCircle(x, y, 8).setColor('#fff').fillCircle(x-3, y-3, 3);
}
function drawTime() {
const R = Bangle.appRect;
g.setBgColor('#000');
g.clear();
Bangle.drawWidgets();
g.reset();
// pcb
g.setColor('#030').fillRect(R.x, R.y, R.x2, R.y2);
screw(R.x + 8, R.y + 8)
screw(R.x2 - 8, R.y + 8)
screw(R.x + 8, R.y2 - 8)
screw(R.x2 - 8, R.y2 - 8)
for(let i=0; i<6; i++) {
g.setColor('#fff');
g.drawLine(24 + i * 9, 70, 24 + i * 9, 110);
g.drawLine(24 + i * 9, 110, 54 + i * 9, 140);
}
ohmA(29, 90);
ohmA(56, 90);
ohmB(80, 90);
screw(90, 110)
// led
led(50, R.y+10);
led(70, R.y+10);
ohmB(20, R.y + 10);
ohmB(90, R.y + 2);
ohmB(90, R.y + 14);
heart(10, 52);
heart(50, 52);
heart(90, 52);
g.setColor('#666');
for (let i=0; i<6; i++) {
g.fillCircle(110 + i*10, 80+10, 3);
g.fillCircle(110 + i*10, 110+10, 3);
}
g.setColor('#000');
g.fillRect(110, 80+10, 170, 110+10);
g.setColor('#666');
g.drawRect(110, 80+10, 170, 110+10);
g.setFont('6x8').setColor('#666').drawString('AH-118080\n0WT 18-001', 112, 85+10);
resin();
let d = Date();
let t = d.getSeconds()*1000 + d.getMilliseconds();
idTimeout = setTimeout(drawTime, 60000 - t); // time till next minute
}
// special function to handle display switch on
Bangle.on('lcdPower', function(on){
if (on) {
drawTime();
} else {
if(idTimeout) {
clearTimeout(idTimeout);
}
}
});
// Show launcher when button pressed
Bangle.setUI("clock");
Bangle.loadWidgets();
drawTime();

View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwkAqoA/AHlUmYADC69FC601C600C60yC6x4SAANTPCgyFkowTSCgACkZ4TAAVDPCwvCPCaqEPCSnDPCZeDmc0Uyh4TqQXFPCBGEPCQWFPCClEPCSlDmjZDdiUlMYZ4NIwg0EPBpGEDoh4NIIYpCPCDqGMoUydh4oDPB5GGPCBGGG4h4CGQ6HDN4gIEqkjSY4+DBYreDSZINDHYpoDKYw9FTww5DC5BGJPAg8MQQw6DBpBGJWIsyCwtUkQABZhA7CkjYKfJIsGAB9UCqgA/ACQ"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 854 B

View File

@ -0,0 +1,17 @@
{
"id": "alyxclock",
"name": "Alyx Clock",
"version": "0.01",
"description": "A clock in the style of half-life alyx gravity gloves",
"icon": "alyxclock.png",
"screenshots": [{"url":"screenshot_alyxclock.png"}],
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS2"],
"readme": "README.md",
"allow_emulator": true,
"storage": [
{"name":"alyxclock.app.js","url":"alyxclock.app.js"},
{"name":"alyxclock.img","url":"alyxclock.icon.js","evaluate":true}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -26,3 +26,5 @@
0.25: Added option to 'ignore' an app from the message
0.26: Change handling of GPS status to depend on GPS events instead of connection events
0.27: Issue newline before GB commands (solves issue with console.log and ignored commands)
0.28: Navigation messages no longer launch the Maps view unless they're new
0.29: Support for http request xpath return format

View File

@ -203,6 +203,8 @@
event.t="add";
event.src="maps"; // for the icon
event.title="Navigation";
if (require("messages").getMessages().find(m=>m.id=="nav"))
event.t = "modify";
} else {
event.t="remove";
}
@ -229,6 +231,7 @@
//send the request
var req = {t: "http", url:url, id:options.id};
if (options.xpath) req.xpath = options.xpath;
if (options.return) req.return = options.return; // for xpath
if (options.method) req.method = options.method;
if (options.body) req.body = options.body;
if (options.headers) req.headers = options.headers;

View File

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

View File

@ -1,2 +1,5 @@
0.01: New App!
0.02: New config options such as step, meridian, short/long formats, custom prefix/suffix
0.03: Allows showing the month in short or long format by setting `"shortMonth"` to true or false
0.04: Improves touchscreen drag handling for background apps such as Pattern Launcher
0.05: Fixes step count not resetting after a new day starts

View File

@ -18,7 +18,7 @@ Each box can be customized extensively via a simple JSON configuration. You can
## Config File Structure
Here's what an example configuration might look like:
Here's an example of what a configuration might contain:
```
{
@ -37,8 +37,9 @@ Here's what an example configuration might look like:
"boxPos": { "x": 0.5, "y": 0.5 },
"prefix": "", // Adds a string to the beginning of the main string
"suffix": "", // Adds a string to the end of the main string
"disableSuffix": true, // Only used to remove the DayOfMonth suffix
"short": false // Gets long format value of time, meridian, date, or DoW
"disableSuffix": true, // Use to remove DayOfMonth suffix only
"short": false, // Use long format of time, meridian, date, or DoW
"shortMonth": false // Use long format of month within date
},
"bg": { // Can also be removed for no background
@ -51,9 +52,9 @@ __Breakdown of Parameters:__
* **Box Name:** The name of your text box. Box Clock includes functional support for "time", "date", "meridian" (AM/PM), "dow" (Day of Week), "batt" (Battery), and "step" (Step count). You can add additional custom boxes with unique titles.
* **string:** The text string to be displayed inside the box.
* **string:** The text string to be displayed inside the box. This is only required for custom Box Names.
* **font:** The font name given to g.setFont()
* **font:** The font name given to g.setFont().
* **fontSize:** The size of the font.
@ -75,17 +76,34 @@ __Breakdown of Parameters:__
* **suffix:** Adds a string to the end of the main string. For example, you can set "suffix": "%" to display "80%" for the battery percentage.
* **disableSuffix:** Applies only to the "date" box. Set to true to disable the DayOfMonth suffix. This is used to remove the "st","nd","rd", or "th" from the DayOfMonth number
* **disableSuffix:** Applies only to the "date" box. Set to true to disable the DayOfMonth suffix. This is used to remove the "st","nd","rd", or "th" from the DayOfMonth number.
* **short:** Set to false to get the long format value of time, meridian, date, or DayOfWeek. Short formats are used by default,
* **short:** Set to false to get the long format value of time, meridian, date, or DayOfWeek. Short formats are used by default if not specified.
* **shortMonth:** Applies only to the "date" box. Set to false to get the long format value of the month. Short format is used by default if not specified.
* **bg:** This specifies a custom background image, with the img property defining the name of the image file on the Bangle.js storage.
## Multiple Configurations
The app includes a settings menu that allows you to switch between different configurations. The selected configuration is stored in the default JSON file alongside the other configuration data using the selectedConfig property.
__Settings Menu:__
If the selectedConfig property is not present or is set to 0, the app will use the default configuration. To create additional configurations, create separate JSON files with the naming convention boxclk-N.json, where N is the configuration number. The settings menu will list all available configurations.
The app includes a settings menu that allows you to switch between different configurations. The selected configuration is stored as a number in the default `boxclk.json` file using the selectedConfig property.
If the selectedConfig property is not present or is set to 0, the app will use the default configuration. To create additional configurations, create separate JSON files with the naming convention `boxclk-N.json`, where `N` is the configuration number. The settings menu will list all available configurations.
## Example Configs:
To easily try out other configs, download and place the JSON configs and/or background images from below onto your Bangle.js storage. Then go to the Box Clock settings menu to select the new config number. You can also modify them to suit your personal preferences.
__Space Theme:__
- **Config:** [boxclk-1.json](https://github.com/espruino/BangleApps/tree/master/apps/boxclk/boxclk-1.json)
- **Background:** [boxclk.space.img](https://github.com/espruino/BangleApps/tree/master/apps/boxclk/boxclk.space.img) ([Original Source](https://www.pixilart.com/art/fallin-from-outer-space-sr2e0c1a705749a))
__System Color Theme:__
- **Config:** [boxclk-2.json](https://github.com/espruino/BangleApps/tree/master/apps/boxclk/boxclk-2.json)
## Compatibility

View File

@ -4,6 +4,7 @@
* 1. Module dependencies and initial configurations
* ---------------------------------------------------------------
*/
let storage = require("Storage");
let locale = require("locale");
let widgets = require("widget_utils");
@ -30,6 +31,7 @@
* 2. Graphical and visual configurations
* ---------------------------------------------------------------
*/
let w = g.getWidth();
let h = g.getHeight();
let totalWidth, totalHeight;
@ -40,6 +42,7 @@
* 3. Touchscreen Handlers
* ---------------------------------------------------------------
*/
let touchHandler;
let dragHandler;
let movementDistance = 0;
@ -49,6 +52,7 @@
* 4. Font loading function
* ---------------------------------------------------------------
*/
let loadCustomFont = function() {
Graphics.prototype.setFontBrunoAce = function() {
// Actual height 23 (24 - 2)
@ -66,6 +70,7 @@
* 5. Initial settings of boxes and their positions
* ---------------------------------------------------------------
*/
for (let key in boxesConfig) {
if (key === 'bg' && boxesConfig[key].img) {
bgImage = storage.read(boxesConfig[key].img);
@ -167,14 +172,15 @@
* 7. String forming helper functions
* ---------------------------------------------------------------
*/
let isBool = function(val, defaultVal) {
return typeof val !== 'undefined' ? Boolean(val) : defaultVal;
};
let getDate = function(short, disableSuffix) {
let getDate = function(short, shortMonth, disableSuffix) {
const date = new Date();
const dayOfMonth = date.getDate();
const month = short ? locale.month(date, 0) : locale.month(date, 1);
const month = shortMonth ? locale.month(date, 1) : locale.month(date, 0);
const year = date.getFullYear();
let suffix;
if ([1, 21, 31].includes(dayOfMonth)) {
@ -211,6 +217,7 @@
* 8. Main draw function
* ---------------------------------------------------------------
*/
let draw = (function() {
let updatePerMinute = true; // variable to track the state of time display
@ -228,7 +235,12 @@
boxes.meridian.string = modString(boxes.meridian, locale.meridian(date, isBool(boxes.meridian.short, true)));
}
if (boxes.date) {
boxes.date.string = modString(boxes.date, getDate(isBool(boxes.date.short, true), isBool(boxes.date.disableSuffix, false)));
boxes.date.string = (
modString(boxes.date,
getDate(isBool(boxes.date.short, true),
isBool(boxes.date.shortMonth, true),
isBool(boxes.date.disableSuffix, false)
)));
}
if (boxes.dow) {
boxes.dow.string = modString(boxes.dow, getDayOfWeek(date, isBool(boxes.dow.short, true)));
@ -237,7 +249,7 @@
boxes.batt.string = modString(boxes.batt, E.getBattery());
}
if (boxes.step) {
boxes.step.string = modString(boxes.step, Bangle.getStepCount());
boxes.step.string = modString(boxes.step, Bangle.getHealthStatus("day").steps);
}
boxKeys.forEach((boxKey) => {
let boxItem = boxes[boxKey];
@ -267,6 +279,7 @@
* 9. Helper function for touch event
* ---------------------------------------------------------------
*/
let touchInText = function(e, boxItem, boxKey) {
calcBoxSize(boxItem);
const pos = calcBoxPos(boxKey);
@ -291,6 +304,7 @@
* 10. Setup function to configure event handlers
* ---------------------------------------------------------------
*/
let setup = function() {
// ------------------------------------
// Define the touchHandler function
@ -338,6 +352,8 @@
// Define the dragHandler function
// ------------------------------------
dragHandler = function(e) {
// Check if any box is being dragged
if (!Object.values(isDragging).some(Boolean)) return;
// Calculate the movement distance
movementDistance += Math.abs(e.dx) + Math.abs(e.dy);
// Check if the movement distance exceeds a threshold
@ -391,8 +407,9 @@
* 11. Main execution part
* ---------------------------------------------------------------
*/
Bangle.loadWidgets();
widgets.swipeOn();
modSetColor();
setup();
}
}

88
apps/boxclk/boxclk-1.json Normal file
View File

@ -0,0 +1,88 @@
{
"time": {
"font": "6x8",
"fontSize": 3,
"outline": 2,
"color": "#0ff",
"outlineColor": "#00f",
"border": "#0f0",
"xPadding": -1,
"yPadding": -2.5,
"xOffset": 2,
"yOffset": 0,
"boxPos": {
"x": "0.33",
"y": "0.29"
}
},
"meridian": {
"font": "6x8",
"fontSize": 2,
"outline": 1,
"color": "#FF9900",
"outlineColor": "fg",
"border": "#0ff",
"xPadding": -0.5,
"yPadding": -1.5,
"xOffset": 2,
"yOffset": 1,
"boxPos": {
"x": "0.34",
"y": "0.46"
},
"short": false
},
"dow": {
"font": "6x8",
"fontSize": 2,
"outline": 1,
"color": "#000",
"outlineColor": "#fff",
"border": "#0f0",
"xPadding": -0.5,
"yPadding": -0.5,
"xOffset": 1,
"yOffset": 1,
"boxPos": {
"x": "0.5",
"y": "0.82"
}
},
"step": {
"font": "6x8",
"fontSize": 2,
"outline": 1,
"color": "#000",
"outlineColor": "#fff",
"border": "#0f0",
"xPadding": -0.5,
"yPadding": 0.5,
"xOffset": 1,
"yOffset": 1,
"boxPos": {
"x": "0.5",
"y": "0.71"
},
"prefix": "Steps: "
},
"batt": {
"font": "4x6",
"fontSize": 2,
"outline": 1,
"color": "#0ff",
"outlineColor": "#00f",
"border": "#0f0",
"xPadding": -0.5,
"yPadding": -0.5,
"xOffset": 1,
"yOffset": 1,
"boxPos": {
"x": "0.87",
"y": "0.87"
},
"suffix": "%"
},
"bg": {
"img": "boxclk.space.img"
}
}

87
apps/boxclk/boxclk-2.json Normal file
View File

@ -0,0 +1,87 @@
{
"time": {
"font": "6x8",
"fontSize": 5,
"outline": 3,
"color": "bgH",
"outlineColor": "fg",
"border": "#f0f",
"xPadding": -2,
"yPadding": -4.5,
"xOffset": 3,
"yOffset": 0,
"boxPos": {
"x": "0.5",
"y": "0.33"
}
},
"dow": {
"font": "6x8",
"fontSize": 3,
"outline": 1,
"color": "#5ccd73",
"outlineColor": "fg",
"border": "#f0f",
"xPadding": -1,
"yPadding": 0.5,
"xOffset": 2,
"yOffset": 0,
"boxPos": {
"x": "0.5",
"y": "0.57"
},
"short": false
},
"date": {
"font": "6x8",
"fontSize": 2,
"outline": 1,
"color": "#5ccd73",
"outlineColor": "fg",
"border": "#f0f",
"xPadding": -0.5,
"yPadding": 0.5,
"xOffset": 1,
"yOffset": 0,
"boxPos": {
"x": "0.5",
"y": "0.75"
},
"shortMonth": false,
"disableSuffix": true
},
"step": {
"font": "4x6",
"fontSize": 3,
"outline": 2,
"color": "bgH",
"outlineColor": "fg",
"border": "#f0f",
"xPadding": -1,
"yPadding": 0.5,
"xOffset": 2,
"yOffset": 1,
"boxPos": {
"x": "0.5",
"y": "0.92"
},
"prefix": "Steps: "
},
"batt": {
"font": "4x6",
"fontSize": 3,
"outline": 2,
"color": "bgH",
"outlineColor": "fg",
"border": "#f0f",
"xPadding": -1,
"yPadding": -1,
"xOffset": 2,
"yOffset": 2,
"boxPos": {
"x": "0.85",
"y": "0.08"
},
"suffix": "%"
}
}

Binary file not shown.

View File

@ -1,12 +1,13 @@
{
"id": "boxclk",
"name": "Box Clock",
"version": "0.02",
"version": "0.05",
"description": "A customizable clock with configurable text boxes that can be positioned to show your favorite background",
"icon": "app.png",
"screenshots": [
{"url":"screenshot.png"},
{"url":"screenshot-1.png"}
{"url":"screenshot-1.png"},
{"url":"screenshot-2.png"}
],
"type": "clock",
"tags": "clock",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1,411 +1,413 @@
var __assign = Object.assign;
var Layout = require("Layout");
Bangle.loadWidgets();
Bangle.drawWidgets();
var HRM_MIN_CONFIDENCE = 75;
var services = ["0x180d", "0x181a", "0x1819"];
var acc;
var bar;
var gps;
var hrm;
var hrmAny;
var mag;
var btnsShown = false;
var prevBtnsShown = undefined;
var hrmAnyClear;
var settings = {
bar: false,
gps: false,
hrm: false,
mag: false,
};
var idToName = {
acc: "Acceleration",
bar: "Barometer",
gps: "GPS",
hrm: "HRM",
mag: "Magnetometer",
};
var infoFont = "6x8:2";
var colour = {
on: "#0f0",
off: "#fff",
};
var makeToggle = function (id) { return function () {
settings[id] = !settings[id];
var entry = btnLayout[id];
var col = settings[id] ? colour.on : colour.off;
entry.btnBorder = entry.col = col;
btnLayout.update();
btnLayout.render();
enableSensors();
}; };
var btnStyle = {
font: "Vector:14",
fillx: 1,
filly: 1,
col: g.theme.fg,
bgCol: g.theme.bg,
btnBorder: "#fff",
};
var btnLayout = new Layout({
type: "v",
c: [
{
type: "h",
c: [
__assign({ type: "btn", label: idToName.bar, id: "bar", cb: makeToggle('bar') }, btnStyle),
__assign({ type: "btn", label: idToName.gps, id: "gps", cb: makeToggle('gps') }, btnStyle),
]
},
{
type: "h",
c: [
__assign({ type: "btn", label: idToName.hrm, id: "hrm", cb: makeToggle('hrm') }, btnStyle),
__assign({ type: "btn", label: idToName.mag, id: "mag", cb: makeToggle('mag') }, btnStyle),
]
},
{
type: "h",
c: [
__assign(__assign({ type: "btn", label: idToName.acc, id: "acc", cb: function () { } }, btnStyle), { col: colour.on, btnBorder: colour.on }),
__assign({ type: "btn", label: "Back", cb: function () {
setBtnsShown(false);
} }, btnStyle),
]
}
]
}, {
lazy: true,
back: function () {
setBtnsShown(false);
},
});
var setBtnsShown = function (b) {
btnsShown = b;
hook(!btnsShown);
setIntervals();
redraw();
};
var drawInfo = function (force) {
var _a = Bangle.appRect, y = _a.y, x = _a.x, w = _a.w;
var mid = x + w / 2;
var drawn = false;
if (!force && !bar && !gps && !hrm && !mag)
return;
g.reset()
.clearRect(Bangle.appRect)
.setFont(infoFont)
.setFontAlign(0, -1);
if (bar) {
g.drawString("".concat(bar.altitude.toFixed(1), "m"), mid, y);
y += g.getFontHeight();
g.drawString("".concat(bar.pressure.toFixed(1), " hPa"), mid, y);
y += g.getFontHeight();
g.drawString("".concat(bar.temperature.toFixed(1), "C"), mid, y);
y += g.getFontHeight();
drawn = true;
}
if (gps) {
g.drawString("".concat(gps.lat.toFixed(4), " lat, ").concat(gps.lon.toFixed(4), " lon"), mid, y);
y += g.getFontHeight();
g.drawString("".concat(gps.alt, "m (").concat(gps.satellites, " sat)"), mid, y);
y += g.getFontHeight();
drawn = true;
}
if (hrm) {
g.drawString("".concat(hrm.bpm, " BPM (").concat(hrm.confidence, "%)"), mid, y);
y += g.getFontHeight();
drawn = true;
}
else if (hrmAny) {
g.drawString("~".concat(hrmAny.bpm, " BPM (").concat(hrmAny.confidence, "%)"), mid, y);
y += g.getFontHeight();
drawn = true;
if (!settings.hrm && !hrmAnyClear) {
hrmAnyClear = setTimeout(function () {
hrmAny = undefined;
hrmAnyClear = undefined;
}, 10000);
}
}
if (mag) {
g.drawString("".concat(mag.x, " ").concat(mag.y, " ").concat(mag.z), mid, y);
y += g.getFontHeight();
g.drawString("heading: ".concat(mag.heading.toFixed(1)), mid, y);
y += g.getFontHeight();
drawn = true;
}
if (!drawn) {
if (!force || Object.values(settings).every(function (x) { return !x; })) {
g.drawString("swipe to enable", mid, y);
}
else {
g.drawString("events pending", mid, y);
}
y += g.getFontHeight();
}
};
var onTap = function () {
setBtnsShown(true);
};
var redraw = function () {
if (btnsShown) {
if (!prevBtnsShown) {
prevBtnsShown = btnsShown;
Bangle.removeListener("swipe", onTap);
btnLayout.setUI();
btnLayout.forgetLazyState();
g.clearRect(Bangle.appRect);
}
btnLayout.render();
}
else {
if (prevBtnsShown) {
prevBtnsShown = btnsShown;
Bangle.setUI();
Bangle.on("swipe", onTap);
drawInfo(true);
}
else {
drawInfo();
}
}
};
var encodeHrm = function (hrm) {
return [0, hrm.bpm];
};
encodeHrm.maxLen = 2;
var encodePressure = function (data) {
return toByteArray(Math.round(data.pressure * 10), 4, false);
};
encodePressure.maxLen = 4;
var encodeElevation = function (data) {
return toByteArray(Math.round(data.altitude * 100), 3, true);
};
encodeElevation.maxLen = 3;
var encodeTemp = function (data) {
return toByteArray(Math.round(data.temperature * 10), 2, true);
};
encodeTemp.maxLen = 2;
var encodeGps = function (data) {
var speed = toByteArray(Math.round(1000 * data.speed / 36), 2, false);
var lat = toByteArray(Math.round(data.lat * 10000000), 4, true);
var lon = toByteArray(Math.round(data.lon * 10000000), 4, true);
var elevation = toByteArray(Math.round(data.alt * 100), 3, true);
var heading = toByteArray(Math.round(data.course * 100), 2, false);
return [
157,
2,
speed[0], speed[1],
lat[0], lat[1], lat[2], lat[3],
lon[0], lon[1], lon[2], lon[3],
elevation[0], elevation[1], elevation[2],
heading[0], heading[1]
];
};
encodeGps.maxLen = 17;
var encodeGpsHeadingOnly = function (data) {
var heading = toByteArray(Math.round(data.heading * 100), 2, false);
return [
16,
16,
heading[0], heading[1]
];
};
encodeGpsHeadingOnly.maxLen = 17;
var encodeMag = function (data) {
var x = toByteArray(data.x, 2, true);
var y = toByteArray(data.y, 2, true);
var z = toByteArray(data.z, 2, true);
return [x[0], x[1], y[0], y[1], z[0], z[1]];
};
encodeMag.maxLen = 6;
var toByteArray = function (value, numberOfBytes, isSigned) {
var byteArray = new Array(numberOfBytes);
if (isSigned && (value < 0)) {
value += 1 << (numberOfBytes * 8);
}
for (var index = 0; index < numberOfBytes; index++) {
byteArray[index] = (value >> (index * 8)) & 0xff;
}
return byteArray;
};
var enableSensors = function () {
Bangle.setBarometerPower(settings.bar, "btadv");
if (!settings.bar)
bar = undefined;
Bangle.setGPSPower(settings.gps, "btadv");
if (!settings.gps)
gps = undefined;
Bangle.setHRMPower(settings.hrm, "btadv");
if (!settings.hrm)
hrm = hrmAny = undefined;
Bangle.setCompassPower(settings.mag, "btadv");
if (!settings.mag)
mag = undefined;
};
var haveServiceData = function (serv) {
switch (serv) {
case "0x180d": return !!hrm;
case "0x181a": return !!(bar || mag);
case "0x1819": return !!(gps && gps.lat && gps.lon || mag);
}
};
var serviceToAdvert = function (serv, initial) {
var _a, _b, _c;
if (initial === void 0) { initial = false; }
switch (serv) {
case "0x180d":
if (hrm || initial) {
var o = {
maxLen: encodeHrm.maxLen,
readable: true,
notify: true,
};
if (hrm) {
o.value = encodeHrm(hrm);
hrm = undefined;
}
return _a = {}, _a["0x2a37"] = o, _a;
}
return {};
case "0x1819":
if (gps || initial) {
var o = {
maxLen: encodeGps.maxLen,
readable: true,
notify: true,
};
if (gps) {
o.value = encodeGps(gps);
gps = undefined;
}
return _b = {}, _b["0x2a67"] = o, _b;
}
else if (mag) {
var o = {
maxLen: encodeGpsHeadingOnly.maxLen,
readable: true,
notify: true,
value: encodeGpsHeadingOnly(mag),
};
return _c = {}, _c["0x2a67"] = o, _c;
}
return {};
case "0x181a": {
var o = {};
if (bar || initial) {
o["0x2a6c"] = {
maxLen: encodeElevation.maxLen,
readable: true,
notify: true,
};
o["0x2A1F"] = {
maxLen: encodeTemp.maxLen,
readable: true,
notify: true,
};
o["0x2a6d"] = {
maxLen: encodePressure.maxLen,
readable: true,
notify: true,
};
if (bar) {
o["0x2a6c"].value = encodeElevation(bar);
o["0x2A1F"].value = encodeTemp(bar);
o["0x2a6d"].value = encodePressure(bar);
bar = undefined;
}
}
if (mag || initial) {
o["0x2aa1"] = {
maxLen: encodeMag.maxLen,
readable: true,
notify: true,
};
if (mag) {
o["0x2aa1"].value = encodeMag(mag);
}
}
return o;
}
}
};
var getBleAdvert = function (map, all) {
if (all === void 0) { all = false; }
var advert = {};
for (var _i = 0, services_1 = services; _i < services_1.length; _i++) {
var serv = services_1[_i];
if (all || haveServiceData(serv)) {
advert[serv] = map(serv);
}
}
mag = undefined;
return advert;
};
var updateServices = function () {
var newAdvert = getBleAdvert(serviceToAdvert);
NRF.updateServices(newAdvert);
};
var onAccel = function (newAcc) { return acc = newAcc; };
var onPressure = function (newBar) { return bar = newBar; };
var onGPS = function (newGps) { return gps = newGps; };
var onHRM = function (newHrm) {
if (newHrm.confidence >= HRM_MIN_CONFIDENCE)
hrm = newHrm;
hrmAny = newHrm;
};
var onMag = function (newMag) { return mag = newMag; };
var hook = function (enable) {
if (enable) {
Bangle.on("accel", onAccel);
Bangle.on("pressure", onPressure);
Bangle.on("GPS", onGPS);
Bangle.on("HRM", onHRM);
Bangle.on("mag", onMag);
}
else {
Bangle.removeListener("accel", onAccel);
Bangle.removeListener("pressure", onPressure);
Bangle.removeListener("GPS", onGPS);
Bangle.removeListener("HRM", onHRM);
Bangle.removeListener("mag", onMag);
}
};
var setIntervals = function (locked, connected) {
if (locked === void 0) { locked = Bangle.isLocked(); }
if (connected === void 0) { connected = NRF.getSecurityStatus().connected; }
changeInterval(redrawInterval, locked ? 15000 : 5000);
if (connected) {
var interval = btnsShown ? 5000 : 1000;
if (bleInterval) {
changeInterval(bleInterval, interval);
}
else {
bleInterval = setInterval(updateServices, interval);
}
}
else if (bleInterval) {
clearInterval(bleInterval);
bleInterval = undefined;
}
};
var redrawInterval = setInterval(redraw, 1000);
Bangle.on("lock", function (locked) { return setIntervals(locked); });
var bleInterval;
NRF.on("connect", function () { return setIntervals(undefined, true); });
NRF.on("disconnect", function () { return setIntervals(undefined, false); });
setIntervals();
setBtnsShown(true);
enableSensors();
{
var ad = getBleAdvert(function (serv) { return serviceToAdvert(serv, true); }, true);
var adServices = Object
.keys(ad)
.map(function (k) { return k.replace("0x", ""); });
NRF.setServices(ad, {
advertise: adServices,
uart: false,
var __assign = Object.assign;
var Layout_1 = require("Layout");
Bangle.loadWidgets();
Bangle.drawWidgets();
var HRM_MIN_CONFIDENCE_1 = 75;
var services_1 = ["0x180d", "0x181a", "0x1819"];
var acc_1;
var bar_1;
var gps_1;
var hrm_1;
var hrmAny_1;
var mag_1;
var btnsShown_1 = false;
var prevBtnsShown_1 = undefined;
var hrmAnyClear_1;
var settings_1 = {
bar: false,
gps: false,
hrm: false,
mag: false,
};
var idToName = {
acc: "Acceleration",
bar: "Barometer",
gps: "GPS",
hrm: "HRM",
mag: "Magnetometer",
};
var infoFont_1 = "6x8:2";
var colour_1 = {
on: "#0f0",
off: "#fff",
};
var makeToggle = function (id) { return function () {
settings_1[id] = !settings_1[id];
var entry = btnLayout_1[id];
var col = settings_1[id] ? colour_1.on : colour_1.off;
entry.btnBorder = entry.col = col;
btnLayout_1.update();
btnLayout_1.render();
enableSensors_1();
}; };
var btnStyle = {
font: "Vector:14",
fillx: 1,
filly: 1,
col: g.theme.fg,
bgCol: g.theme.bg,
btnBorder: "#fff",
};
var btnLayout_1 = new Layout_1({
type: "v",
c: [
{
type: "h",
c: [
__assign({ type: "btn", label: idToName.bar, id: "bar", cb: makeToggle('bar') }, btnStyle),
__assign({ type: "btn", label: idToName.gps, id: "gps", cb: makeToggle('gps') }, btnStyle),
]
},
{
type: "h",
c: [
__assign({ type: "btn", label: idToName.hrm, id: "hrm", cb: makeToggle('hrm') }, btnStyle),
__assign({ type: "btn", label: idToName.mag, id: "mag", cb: makeToggle('mag') }, btnStyle),
]
},
{
type: "h",
c: [
__assign(__assign({ type: "btn", label: idToName.acc, id: "acc", cb: function () { } }, btnStyle), { col: colour_1.on, btnBorder: colour_1.on }),
__assign({ type: "btn", label: "Back", cb: function () {
setBtnsShown_1(false);
} }, btnStyle),
]
}
]
}, {
lazy: true,
back: function () {
setBtnsShown_1(false);
},
});
var setBtnsShown_1 = function (b) {
btnsShown_1 = b;
hook_1(!btnsShown_1);
setIntervals_1();
redraw_1();
};
var drawInfo_1 = function (force) {
var _a = Bangle.appRect, y = _a.y, x = _a.x, w = _a.w;
var mid = x + w / 2;
var drawn = false;
if (!force && !bar_1 && !gps_1 && !hrm_1 && !mag_1)
return;
g.reset()
.clearRect(Bangle.appRect)
.setFont(infoFont_1)
.setFontAlign(0, -1);
if (bar_1) {
g.drawString("".concat(bar_1.altitude.toFixed(1), "m"), mid, y);
y += g.getFontHeight();
g.drawString("".concat(bar_1.pressure.toFixed(1), " hPa"), mid, y);
y += g.getFontHeight();
g.drawString("".concat(bar_1.temperature.toFixed(1), "C"), mid, y);
y += g.getFontHeight();
drawn = true;
}
if (gps_1) {
g.drawString("".concat(gps_1.lat.toFixed(4), " lat, ").concat(gps_1.lon.toFixed(4), " lon"), mid, y);
y += g.getFontHeight();
g.drawString("".concat(gps_1.alt, "m (").concat(gps_1.satellites, " sat)"), mid, y);
y += g.getFontHeight();
drawn = true;
}
if (hrm_1) {
g.drawString("".concat(hrm_1.bpm, " BPM (").concat(hrm_1.confidence, "%)"), mid, y);
y += g.getFontHeight();
drawn = true;
}
else if (hrmAny_1) {
g.drawString("~".concat(hrmAny_1.bpm, " BPM (").concat(hrmAny_1.confidence, "%)"), mid, y);
y += g.getFontHeight();
drawn = true;
if (!settings_1.hrm && !hrmAnyClear_1) {
hrmAnyClear_1 = setTimeout(function () {
hrmAny_1 = undefined;
hrmAnyClear_1 = undefined;
}, 10000);
}
}
if (mag_1) {
g.drawString("".concat(mag_1.x, " ").concat(mag_1.y, " ").concat(mag_1.z), mid, y);
y += g.getFontHeight();
g.drawString("heading: ".concat(mag_1.heading.toFixed(1)), mid, y);
y += g.getFontHeight();
drawn = true;
}
if (!drawn) {
if (!force || Object.values(settings_1).every(function (x) { return !x; })) {
g.drawString("swipe to enable", mid, y);
}
else {
g.drawString("events pending", mid, y);
}
y += g.getFontHeight();
}
};
var onTap_1 = function () {
setBtnsShown_1(true);
};
var redraw_1 = function () {
if (btnsShown_1) {
if (!prevBtnsShown_1) {
prevBtnsShown_1 = btnsShown_1;
Bangle.removeListener("swipe", onTap_1);
btnLayout_1.setUI();
btnLayout_1.forgetLazyState();
g.clearRect(Bangle.appRect);
}
btnLayout_1.render();
}
else {
if (prevBtnsShown_1) {
prevBtnsShown_1 = btnsShown_1;
Bangle.setUI();
Bangle.on("swipe", onTap_1);
drawInfo_1(true);
}
else {
drawInfo_1();
}
}
};
var encodeHrm_1 = function (hrm) {
return [0, hrm.bpm];
};
encodeHrm_1.maxLen = 2;
var encodePressure_1 = function (data) {
return toByteArray_1(Math.round(data.pressure * 10), 4, false);
};
encodePressure_1.maxLen = 4;
var encodeElevation_1 = function (data) {
return toByteArray_1(Math.round(data.altitude * 100), 3, true);
};
encodeElevation_1.maxLen = 3;
var encodeTemp_1 = function (data) {
return toByteArray_1(Math.round(data.temperature * 10), 2, true);
};
encodeTemp_1.maxLen = 2;
var encodeGps_1 = function (data) {
var speed = toByteArray_1(Math.round(1000 * data.speed / 36), 2, false);
var lat = toByteArray_1(Math.round(data.lat * 10000000), 4, true);
var lon = toByteArray_1(Math.round(data.lon * 10000000), 4, true);
var elevation = toByteArray_1(Math.round(data.alt * 100), 3, true);
var heading = toByteArray_1(Math.round(data.course * 100), 2, false);
return [
157,
2,
speed[0], speed[1],
lat[0], lat[1], lat[2], lat[3],
lon[0], lon[1], lon[2], lon[3],
elevation[0], elevation[1], elevation[2],
heading[0], heading[1]
];
};
encodeGps_1.maxLen = 17;
var encodeGpsHeadingOnly_1 = function (data) {
var heading = toByteArray_1(Math.round(data.heading * 100), 2, false);
return [
16,
16,
heading[0], heading[1]
];
};
encodeGpsHeadingOnly_1.maxLen = 17;
var encodeMag_1 = function (data) {
var x = toByteArray_1(data.x, 2, true);
var y = toByteArray_1(data.y, 2, true);
var z = toByteArray_1(data.z, 2, true);
return [x[0], x[1], y[0], y[1], z[0], z[1]];
};
encodeMag_1.maxLen = 6;
var toByteArray_1 = function (value, numberOfBytes, isSigned) {
var byteArray = new Array(numberOfBytes);
if (isSigned && (value < 0)) {
value += 1 << (numberOfBytes * 8);
}
for (var index = 0; index < numberOfBytes; index++) {
byteArray[index] = (value >> (index * 8)) & 0xff;
}
return byteArray;
};
var enableSensors_1 = function () {
Bangle.setBarometerPower(settings_1.bar, "btadv");
if (!settings_1.bar)
bar_1 = undefined;
Bangle.setGPSPower(settings_1.gps, "btadv");
if (!settings_1.gps)
gps_1 = undefined;
Bangle.setHRMPower(settings_1.hrm, "btadv");
if (!settings_1.hrm)
hrm_1 = hrmAny_1 = undefined;
Bangle.setCompassPower(settings_1.mag, "btadv");
if (!settings_1.mag)
mag_1 = undefined;
};
var haveServiceData_1 = function (serv) {
switch (serv) {
case "0x180d": return !!hrm_1;
case "0x181a": return !!(bar_1 || mag_1);
case "0x1819": return !!(gps_1 && gps_1.lat && gps_1.lon || mag_1);
}
};
var serviceToAdvert_1 = function (serv, initial) {
var _a, _b, _c;
if (initial === void 0) { initial = false; }
switch (serv) {
case "0x180d":
if (hrm_1 || initial) {
var o = {
maxLen: encodeHrm_1.maxLen,
readable: true,
notify: true,
};
if (hrm_1) {
o.value = encodeHrm_1(hrm_1);
hrm_1 = undefined;
}
return _a = {}, _a["0x2a37"] = o, _a;
}
return {};
case "0x1819":
if (gps_1 || initial) {
var o = {
maxLen: encodeGps_1.maxLen,
readable: true,
notify: true,
};
if (gps_1) {
o.value = encodeGps_1(gps_1);
gps_1 = undefined;
}
return _b = {}, _b["0x2a67"] = o, _b;
}
else if (mag_1) {
var o = {
maxLen: encodeGpsHeadingOnly_1.maxLen,
readable: true,
notify: true,
value: encodeGpsHeadingOnly_1(mag_1),
};
return _c = {}, _c["0x2a67"] = o, _c;
}
return {};
case "0x181a": {
var o = {};
if (bar_1 || initial) {
o["0x2a6c"] = {
maxLen: encodeElevation_1.maxLen,
readable: true,
notify: true,
};
o["0x2A1F"] = {
maxLen: encodeTemp_1.maxLen,
readable: true,
notify: true,
};
o["0x2a6d"] = {
maxLen: encodePressure_1.maxLen,
readable: true,
notify: true,
};
if (bar_1) {
o["0x2a6c"].value = encodeElevation_1(bar_1);
o["0x2A1F"].value = encodeTemp_1(bar_1);
o["0x2a6d"].value = encodePressure_1(bar_1);
bar_1 = undefined;
}
}
if (mag_1 || initial) {
o["0x2aa1"] = {
maxLen: encodeMag_1.maxLen,
readable: true,
notify: true,
};
if (mag_1) {
o["0x2aa1"].value = encodeMag_1(mag_1);
}
}
return o;
}
}
};
var getBleAdvert_1 = function (map, all) {
if (all === void 0) { all = false; }
var advert = {};
for (var _i = 0, services_2 = services_1; _i < services_2.length; _i++) {
var serv = services_2[_i];
if (all || haveServiceData_1(serv)) {
advert[serv] = map(serv);
}
}
mag_1 = undefined;
return advert;
};
var updateServices_1 = function () {
var newAdvert = getBleAdvert_1(serviceToAdvert_1);
NRF.updateServices(newAdvert);
};
var onAccel_1 = function (newAcc) { return acc_1 = newAcc; };
var onPressure_1 = function (newBar) { return bar_1 = newBar; };
var onGPS_1 = function (newGps) { return gps_1 = newGps; };
var onHRM_1 = function (newHrm) {
if (newHrm.confidence >= HRM_MIN_CONFIDENCE_1)
hrm_1 = newHrm;
hrmAny_1 = newHrm;
};
var onMag_1 = function (newMag) { return mag_1 = newMag; };
var hook_1 = function (enable) {
if (enable) {
Bangle.on("accel", onAccel_1);
Bangle.on("pressure", onPressure_1);
Bangle.on("GPS", onGPS_1);
Bangle.on("HRM", onHRM_1);
Bangle.on("mag", onMag_1);
}
else {
Bangle.removeListener("accel", onAccel_1);
Bangle.removeListener("pressure", onPressure_1);
Bangle.removeListener("GPS", onGPS_1);
Bangle.removeListener("HRM", onHRM_1);
Bangle.removeListener("mag", onMag_1);
}
};
var setIntervals_1 = function (locked, connected) {
if (locked === void 0) { locked = Bangle.isLocked(); }
if (connected === void 0) { connected = NRF.getSecurityStatus().connected; }
changeInterval(redrawInterval_1, locked ? 15000 : 5000);
if (connected) {
var interval = btnsShown_1 ? 5000 : 1000;
if (bleInterval_1) {
changeInterval(bleInterval_1, interval);
}
else {
bleInterval_1 = setInterval(updateServices_1, interval);
}
}
else if (bleInterval_1) {
clearInterval(bleInterval_1);
bleInterval_1 = undefined;
}
};
var redrawInterval_1 = setInterval(redraw_1, 1000);
Bangle.on("lock", function (locked) { return setIntervals_1(locked); });
var bleInterval_1;
NRF.on("connect", function () { return setIntervals_1(undefined, true); });
NRF.on("disconnect", function () { return setIntervals_1(undefined, false); });
setIntervals_1();
setBtnsShown_1(true);
enableSensors_1();
{
var ad = getBleAdvert_1(function (serv) { return serviceToAdvert_1(serv, true); }, true);
var adServices = Object
.keys(ad)
.map(function (k) { return k.replace("0x", ""); });
NRF.setServices(ad, {
advertise: adServices,
uart: false,
});
}
}

View File

@ -1,4 +1,5 @@
// ts helpers:
{
// @ts-ignore helper
const __assign = Object.assign;
const Layout = require("Layout");
@ -713,3 +714,4 @@ enableSensors();
},
);
}
}

View File

@ -18,5 +18,6 @@
{"name":"bthrm.settings.js","url":"settings.js"},
{"name":"bthrm","url":"lib.js"},
{"name":"bthrm.default.json","url":"default.json"}
]
],
"data": [{"name":"bthrm.json"}]
}

View File

@ -15,5 +15,6 @@
{"name":"bwclk.app.js","url":"app.js"},
{"name":"bwclk.img","url":"app-icon.js","evaluate":true},
{"name":"bwclk.settings.js","url":"settings.js"}
]
],
"data":[{"name":"bwclk.setting.json"}]
}

View File

@ -39,5 +39,10 @@
"name": "bwclklite.settings.js",
"url": "settings.js"
}
],
"data": [
{
"name": "bwclklite.setting.json"
}
]
}

View File

@ -12,7 +12,10 @@
"readme": "README.md",
"storage": [
{ "name": "cassioWatch.app.js", "url": "app.js" },
{"name":"cassioWatch.settings.js","url":"settings.js"},
{ "name": "cassioWatch.settings.js","url": "settings.js" },
{ "name": "cassioWatch.img", "url": "icon.js", "evaluate": true }
],
"data": [
{ "name": "cassioWatch.settings.json" }
]
}

View File

@ -2,3 +2,4 @@
0.02: Support BangleJS2
0.03: Added threshold
0.04: Added notification
0.05: Fixed boot

View File

@ -2,7 +2,7 @@
const pin = process.env.HWVERSION === 2 ? D3 : D30;
var id;
Bangle.on('charging', (charging) => {
function gent(charging) {
if (charging) {
if (!id) {
var max = 0;
@ -37,5 +37,8 @@
require('notify').hide({id: 'chargent'});
}
}
});
}
Bangle.on('charging', gent);
if (Bangle.isCharging()) gent(true);
})();

View File

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

View File

@ -11,5 +11,6 @@
"storage": [
{"name":"chargerot.boot.js","url":"boot.js"},
{"name":"chargerot.settings.js","url":"settings.js"}
]
],
"data":[{"name":"chargerot.settings.json"}]
}

1
apps/chess/app-icon.js Normal file
View File

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4A/AEnPAEAv/wAAcF6XWAAYdFBQgLLF/4v/F/4vTFKoLGF/4v/F/4v/F4QpWBQov/F7UslgKBAYIABEgIDDF/4v/F4es1gvTdKoLBFYYAHF/4vTmQvuvQwJFxAvbAAOIxIAFFxIvzFKYLG6CMNF8GsF92BdpwvfqwvtRwgvQAC+IxIAJF5QAYF93OwEyRwqSPACwsJGEov/AH4A/AH4AwA="))

283
apps/chess/app.js Normal file
View File

@ -0,0 +1,283 @@
// Using p4wn chess engine: https://p4wn.sourceforge.net/ | https://github.com/douglasbagnall/p4wn
const engine = require("chessengine");
Bangle.loadWidgets(); // load before first appRect call
const FIELD_WIDTH = Bangle.appRect.w/8;
const FIELD_HEIGHT = Bangle.appRect.h/8;
const SETTINGS_FILE = "chess.json";
const DEFAULT_TIMEOUT = Bangle.getOptions().lockTimeout;
const ICON_SIZE=45;
const ICON_BISHOP = require("heatshrink").decompress(atob("lstwMB/4Ac/wFE4IED/kPAofgn4FDGon8j4QEBQgQE4EHBQcACwfAgF/BQYWD8EAHAX+NgI4C+AQEwAQDDYIhDDYMDCAQKBGQQsHHogKDCAJODCAI3CHoQKCHoIQDHoIQCFgoQBFgfgIQYmBEIQECKgIrCBYQKDC4OBg/8iCvEAC+AA="));
const ICON_PAWN = require("heatshrink").decompress(atob("lstwMB/4At/AFEGon4h4FDwE/AgX8CAngCAkAv4bDgYbECAf4gAhD4AhD/kAg4mDCAkACAYbBEIYQBG4gbDEII9DFhXAgEfBQYWDEwJUC/wKBGQXwCAgEBE4RCBCAYmBCAQmCCAQmBCAbdCCAIbCQ4gAYwA="));
const ICON_KING = require("heatshrink").decompress(atob("lstwMB/4Ac/wFE+4KEh4FD+F/AofvCwgKE+IKEg4bEj4FDwADC/k8g+HAoJhCC4PwAoQXBNod//AECgYfBAoUP/gQE8AQEBQcfCAaLBCAZmBEIZuBBQgyDJAIWCPgXAEAQWDBQRUCPgQnBHgJqBLwYhDOwRvDGQc/EIaSDCwLedwAA=="));
const ICON_QUEEN = require("heatshrink").decompress(atob("lstwMB/4Ac/l/AgXn4PzAgP+j0Ph4FB8FwuE///PgeDwPn/k8n0+j0f4Hz+Px8F+g/Px+fgf4vgACn/jAAf/x8Pj0en/8vAsB+P/+PBwcHj//w0MjEwJgMwsHBw5CBwMEhBDBPoR6B/gFCDYPgAoRZBAgUH//4AoQbB4AbDCAYbBCAZ1CAgJ7CwAKDGQQmBCAYmBEIQmC+AQEDYQQBDYQQCFgo3CXQIsFBYIEDACmAA="));
const ICON_ROOK = require("heatshrink").decompress(atob("lstwMB/4Ax/0HgPAAoPwnEOg4FBwBFBn///gEBI4XgAoMPAoJWCv4QDDYXwBQf/4AKD/wmDCARuDGQImCEIQbCGQMDCAQKBj4EB/AFBBQQsgDYQQCNQQhCOog3CCAQ3BEIRvCAoSRCE4IxCKgQmCKgYAZwA="));
const ICON_KNIGHT = require("heatshrink").decompress(atob("lstwMB/4Ann1/AgX48IKD4UPAgX+gEHAoXwgALDJQMfDYQFBEQWAgBSCBQQcC4AFBn///hnCBQPgAgMDGIQnDGIIQDAgQQBEwQQCGIIQCEwMECAQxBsAQBEwMPCAQmBAIJDB4EPDoM/CAIoBKgP4BQQQB/AzCKgJlIPgQ+COwJlCHoJlDJwJlDS4aBDDYQsCADOA"));
const settings = Object.assign({
state: engine.P4_INITIAL_BOARD,
computer_level: 0, // default to "stupid" which is the fastest
}, require("Storage").readJSON(SETTINGS_FILE,1) || {});
var ovr = Graphics.createArrayBuffer(Bangle.appRect.w,Bangle.appRect.h,2,{msb:true});
const curfield = [4*FIELD_WIDTH, 6*FIELD_HEIGHT]; // e2
const startfield = Array(2);
let piece_sel = 0;
let showmenu = false;
const writeSettings = () => {
settings.state = engine.p4_state2fen(state);
require('Storage').writeJSON(SETTINGS_FILE, settings);
};
const generateBgImage = () => {
var buf = Graphics.createArrayBuffer(Bangle.appRect.w,Bangle.appRect.h,1,{msb:true});
for(let idxrow=0; idxrow<8; idxrow++) {
for(let idxcol=0; idxcol<8; idxcol++) {
const bgCol = idxrow % 2 != idxcol % 2 ? 0 : 1;
const x = idxcol*FIELD_WIDTH;
const y = idxrow*FIELD_HEIGHT;
buf.setColor(bgCol).fillRect({x:x, y:y, w:FIELD_WIDTH, h:FIELD_HEIGHT});
}
}
return {width:buf.getWidth(), height:buf.getHeight(),
buffer:buf.buffer
};
};
const idx2Pos = (idxcol, idxrow) => {
"ram"
return 2*(1+8+1) + (7-idxrow)*(1+8+1) + idxcol + 1;
};
const drawPiece = (buf, x, y, piece) => {
let icon;
switch(piece & ~0x1) {
case engine.P4_PAWN:
icon = ICON_PAWN;
break;
case engine.P4_BISHOP:
icon = ICON_BISHOP;
break;
case engine.P4_KING:
icon = ICON_KING;
break;
case engine.P4_QUEEN:
icon = ICON_QUEEN;
break;
case engine.P4_ROOK:
icon = ICON_ROOK;
break;
case engine.P4_KNIGHT:
icon = ICON_KNIGHT;
break;
}
if (icon) {
const scale = FIELD_HEIGHT/ICON_SIZE;
buf.drawImage(icon, x+(FIELD_WIDTH-(ICON_SIZE*scale))/2, y, {scale: scale});
}
return buf;
};
const drawBoard = () => {
//console.log("Free: " + process.memory().free);
g.setBgColor("#555").setColor("#aaa").drawImage(bgImage, Bangle.appRect.x, Bangle.appRect.y);
for(let idxrow=0; idxrow<8; idxrow++) {
for(let idxcol=0; idxcol<8; idxcol++) {
const x = idxcol*FIELD_WIDTH+Bangle.appRect.x;
const y = idxrow*FIELD_HEIGHT+Bangle.appRect.y;
const pos = idx2Pos(idxcol, idxrow);
const field = state.board[pos];
if (field) {
const fgCol = field & 0x1 ? "#000" : "#fff";
drawPiece(g.setBgColor(fgCol), x, y, field);
}
}
}
};
const roundX = (x) => {
return Math.round(x/FIELD_WIDTH)*FIELD_WIDTH;
};
const roundY = (y) => {
return Math.round(y/FIELD_HEIGHT)*FIELD_HEIGHT;
};
const drawSelectedField = () => {
ovr.clear();
if (!showmenu) {
if (startfield[0] !== undefined && startfield[1] !== undefined) {
// remove piece from startfield
const x = startfield[0];
const y = startfield[1];
ovr.setColor(2).fillRect({x:x, y:y, w:FIELD_WIDTH, h:FIELD_HEIGHT});
}
const x = roundX(curfield[0]);
const y = roundY(curfield[1]);
ovr.setColor(piece_sel ? 1 : 2)
.drawRect({x:x+1, y:y, w:FIELD_WIDTH-2, h:FIELD_HEIGHT})
.drawRect({x:x+2, y:y+1, w:FIELD_WIDTH-4, h:FIELD_HEIGHT-2})
.drawRect({x:x+3, y:y+2, w:FIELD_WIDTH-6, h:FIELD_HEIGHT-4});
if (piece_sel) {
drawPiece(ovr.setBgColor(1), x, y, piece_sel);
ovr.setBgColor(0); // back to transparent
}
}
Bangle.setLCDOverlay({width:ovr.getWidth(), height:ovr.getHeight(),
bpp:2, transparent:0,
palette:new Uint16Array([0, g.toColor("#F00"), g.toColor("#0F0"), 0]),
buffer:ovr.buffer
},Bangle.appRect.x,Bangle.appRect.y);
};
const isInside = (rect, e) => {
return e.x>=rect.x && e.x<rect.x+rect.w
&& e.y>=rect.y && e.y<=rect.y+rect.h;
};
const showAlert = (msg) => {
showmenu = true;
drawSelectedField();
E.showAlert(msg).then(function() {
showmenu = false;
drawBoard();
drawSelectedField();
});
};
const move = (from,to) => {
const res = state.move(from, to);
//console.log(res);
if (!res.ok) {
showAlert("Illegal move");
} else {
if (res.flags & engine.P4_MOVE_FLAG_MATE) {
showAlert("Checkmate or stalemate");
} else if (res.flags & engine.P4_MOVE_FLAG_CHECK) {
showAlert("A king is in check");
} else if (res.flags & engine.P4_MOVE_FLAG_DRAW) {
showAlert("A draw is available");
}
}
return res;
};
const showMessage = (msg) => {
g.setColor("#f00").setFont("4x6:2").setFontAlign(-1,1).drawString(msg, 10, Bangle.appRect.y2-10);
};
// Run
g.reset();
const bgImage = generateBgImage();
let state = engine.p4_fen2state(settings.state);
drawBoard();
drawSelectedField();
Bangle.drawWidgets();
// drag selected field
Bangle.on('drag', (ev) => {
const newx = curfield[0]+ev.dx;
const newy = curfield[1]+ev.dy;
if (newx >= 0 && newx <= 7*FIELD_WIDTH) {
curfield[0] = newx;
}
if (newy >= 0 && newy <= 7*FIELD_HEIGHT) {
curfield[1] = newy;
}
drawSelectedField();
});
// touch to start/stop moving a piece
Bangle.on('touch', (button, xy) => {
if (isInside(Bangle.appRect, xy) && !showmenu) {
if (piece_sel === 0) {
startfield[0] = roundX(curfield[0]);
startfield[1] = roundY(curfield[1]);
const startpos = idx2Pos(startfield[0]/FIELD_WIDTH, startfield[1]/FIELD_HEIGHT);
piece_sel = state.board[startpos];
if (piece_sel === 0) {
startfield[0] = startfield[1] = undefined;
// nothing here, do nothing
return;
}
} else { // piece_sel === 0
const colTo = roundX(curfield[0]);
const rowTo = roundY(curfield[1]);
if (startfield[0] !== colTo || startfield[1] !== rowTo) {
showMessage(/*LANG*/"Moving..");
const posFrom = idx2Pos(startfield[0]/FIELD_WIDTH, startfield[1]/FIELD_HEIGHT);
const posTo = idx2Pos(colTo/FIELD_WIDTH, rowTo/FIELD_HEIGHT);
setTimeout(() => {
if (move(posFrom, posTo).ok) {
// human move ok, update
drawBoard();
drawSelectedField();
// do computer move
Bangle.setLCDTimeout(0.1); // this can take some time, turn off to save power
showMessage(/*LANG*/"Calculating..");
setTimeout(() => {
const compMove = state.findmove(settings.computer_level+1);
const result = move(compMove[0], compMove[1]);
writeSettings();
Bangle.setLCDPower(true);
Bangle.setLocked(false);
Bangle.setLCDTimeout(DEFAULT_TIMEOUT/1000); // restore
if (!showmenu) {
showAlert(result.string);
}
}, 200); // execute after display update
}
}, 100); // execute after display update
} // piece_sel === 0
startfield[0] = startfield[1] = undefined;
piece_sel = 0;
}
drawSelectedField();
}
});
// show menu on button
setWatch(() => {
showmenu = true;
drawSelectedField();
const closeMenu = () => {
showmenu = false;
E.showMenu();
drawBoard();
drawSelectedField();
};
E.showMenu({
"" : { title : /*LANG*/"Chess settings" },
"< Back" : () => closeMenu(),
/*LANG*/"New Game" : () => {
state = engine.p4_fen2state(engine.P4_INITIAL_BOARD);
writeSettings();
closeMenu();
},
/*LANG*/"Undo Move" : () => {
state.jump_to_moveno(-2);
closeMenu();
},
/*LANG*/'Level': {
value: settings.computer_level,
min: 0, max: 4,
format: v => [/*LANG*/'stupid', /*LANG*/'middling', /*LANG*/'default', /*LANG*/'slow', /*LANG*/'slowest'][v],
onchange: v => {
settings.computer_level = v;
writeSettings();
}
},
/*LANG*/"Exit" : () => load(),
});
}, BTN, { repeat: true, edge: "falling" });

BIN
apps/chess/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

1614
apps/chess/engine.js Normal file

File diff suppressed because it is too large Load Diff

17
apps/chess/metadata.json Normal file
View File

@ -0,0 +1,17 @@
{
"id": "chess",
"name": "Chess",
"shortName": "Chess",
"version": "0.01",
"description": "Chess game based on the [p4wn engine](https://p4wn.sourceforge.net/). Drag on the touchscreen to move the green cursor onto a piece, select it with a single touch and drag the now red cursor around. Release the piece with another touch to finish the move. The button opens a menu.",
"icon": "app.png",
"tags": "game",
"supports": ["BANGLEJS2"],
"storage": [
{"name":"chess.app.js","url":"app.js"},
{"name":"chessengine","url":"engine.js"},
{"name":"chess.img","url":"app-icon.js","evaluate":true}
],
"data": [{"name":"chess.json"}],
"screenshots": [ {"url":"screenshot.png"} ]
}

BIN
apps/chess/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -1,3 +1,4 @@
0.01: Initial Creation
0.02: Fixed some sleep bugs. Added a sleep mode toggle
0.03: Reduce busy-loop and code
0.04: Separate buzz-time and sleep-time

View File

@ -1,7 +1,7 @@
{
"id": "chimer",
"name": "Chimer",
"version": "0.03",
"version": "0.04",
"description": "A fork of Hour Chime that adds extra features such as: \n - Buzz or beep on every 60, 30 or 15 minutes. \n - Repeat Chime up to 3 times \n - Set hours to disable chime",
"icon": "widget.png",
"type": "widget",

View File

@ -20,15 +20,16 @@
let count = settings.repeat;
const chime1 = () => {
let p;
if (settings.type === 1) {
Bangle.buzz(100);
p = Bangle.buzz(100);
} else if (settings.type === 2) {
Bangle.beep();
p = Bangle.beep();
} else {
return;
}
if (--count > 0)
setTimeout(chime1, 150);
p.then(() => setTimeout(chime1, 150));
};
chime1();

View File

@ -1,36 +0,0 @@
const enum StopWatchFormat {
HMS,
Colon,
}
type StopWatchSettings = {
format: StopWatchFormat,
};
(back => {
const SETTINGS_FILE = "clkinfostopw.setting.json";
const storage = require("Storage");
const settings: StopWatchSettings = Object.assign(
{ format: StopWatchFormat.HMS },
storage.readJSON(SETTINGS_FILE, true),
);
const save = () => {
storage.writeJSON(SETTINGS_FILE, settings)
};
E.showMenu({
"": { "title": "stopwatch" },
"< Back": back,
"Format": {
value: settings.format,
min: StopWatchFormat.HMS,
max: StopWatchFormat.Colon,
format: v => v === StopWatchFormat.HMS ? "12m34s" : "12:34",
onchange: v => {
settings.format = v;
save();
},
},
});
}) satisfies SettingsFunc

View File

@ -5,6 +5,7 @@
"icon": "app.png",
"type": "clkinfo",
"tags": "clkinfo,sunrise",
"dependencies": {"mylocation":"app"},
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"sunrise.clkinfo.js","url":"clkinfo.js"}

View File

@ -3,4 +3,5 @@
0.03: Reported image for battery now reflects charge level
0.04: On 2v18+ firmware, we can now stop swipe events from being handled by other apps
eg. when a clockinfo is selected, swipes won't affect swipe-down widgets
0.05: Reported image for battery is now transparent (2v18+)
0.05: Reported image for battery is now transparent (2v18+)
0.06: When >1 clockinfo, swiping one back tries to ensure they don't display the same thing

View File

@ -10,7 +10,12 @@ if (stepGoal == undefined) {
stepGoal = d != undefined && d.settings != undefined ? d.settings.goal : 10000;
}
// Load the settings, with defaults
/// How many times has addInteractive been called?
exports.loadCount = 0;
/// A list of all the instances returned by addInteractive
exports.clockInfos = [];
/// Load the settings, with defaults
exports.loadSettings = function() {
return Object.assign({
hrmOn : 0, // 0(Always), 1(Tap)
@ -22,6 +27,7 @@ exports.loadSettings = function() {
);
};
/// Load a list of ClockInfos - this does not cache and reloads each time
exports.load = function() {
var settings = exports.loadSettings();
delete settings.apps; // keep just the basic settings in memory
@ -63,7 +69,7 @@ exports.load = function() {
} else img=atob("GBiBAAABgAADwAAHwAAPgACfAAHOAAPkBgHwDwP4Hwf8Pg/+fB//OD//kD//wD//4D//8D//4B//QB/+AD/8AH/4APnwAHAAACAAAA==");
return {
text : v + "%", v : v, min:0, max:100, img : img
}
};
},
show : function() { this.interval = setInterval(()=>this.emit('redraw'), 60000); Bangle.on("charging", batteryUpdateHandler); batteryUpdateHandler(); },
hide : function() { clearInterval(this.interval); delete this.interval; Bangle.removeListener("charging", batteryUpdateHandler); },
@ -73,7 +79,7 @@ exports.load = function() {
get : () => { let v = Bangle.getHealthStatus("day").steps; return {
text : v, v : v, min : 0, max : stepGoal,
img : atob("GBiBAAcAAA+AAA/AAA/AAB/AAB/gAA/g4A/h8A/j8A/D8A/D+AfH+AAH8AHn8APj8APj8AHj4AHg4AADAAAHwAAHwAAHgAAHgAADAA==")
}},
};},
show : function() { Bangle.on("step", stepUpdateHandler); stepUpdateHandler(); },
hide : function() { Bangle.removeListener("step", stepUpdateHandler); },
},
@ -82,7 +88,7 @@ exports.load = function() {
get : () => { return {
text : (hrm||"--") + " bpm", v : hrm, min : 40, max : 200,
img : atob("GBiBAAAAAAAAAAAAAAAAAAAAAADAAADAAAHAAAHjAAHjgAPngH9n/n82/gA+AAA8AAA8AAAcAAAYAAAYAAAAAAAAAAAAAAAAAAAAAA==")
}},
};},
run : function() {
Bangle.setHRMPower(1,"clkinfo");
if (settings.hrmOn==1/*Tap*/) {
@ -131,11 +137,11 @@ exports.load = function() {
require("Storage").list(/clkinfo.js$/).forEach(fn => {
try{
var a = eval(require("Storage").read(fn))();
var b = menu.find(x => x.name === a.name)
var b = menu.find(x => x.name === a.name);
if(b) b.items = b.items.concat(a.items);
else menu = menu.concat(a);
} catch(e){
console.log("Could not load clock info "+E.toJS(fn))
console.log("Could not load clock info "+E.toJS(fn));
}
});
@ -204,11 +210,12 @@ exports.addInteractive = function(menu, options) {
if ("function" == typeof options) options = {draw:options}; // backwards compatibility
options.index = 0|exports.loadCount;
exports.loadCount = options.index+1;
exports.clockInfos[options.index] = options;
options.focus = options.index==0 && options.x===undefined; // focus if we're the first one loaded and no position has been defined
const appName = (options.app||"default")+":"+options.index;
// load the currently showing clock_infos
let settings = exports.loadSettings()
let settings = exports.loadSettings();
if (settings.apps[appName]) {
let a = settings.apps[appName].a|0;
let b = settings.apps[appName].b|0;
@ -259,6 +266,10 @@ exports.addInteractive = function(menu, options) {
//can happen for dynamic ones (alarms, events)
//in the worst case we come back to 0
} while(menu[options.menuA].items.length==0);
// When we change, ensure we don't display the same thing as another clockinfo if we can avoid it
while ((options.menuB < menu[options.menuA].items.length) &&
exports.clockInfos.some(m => (m!=options) && m.menuA==options.menuA && m.menuB==options.menuB))
options.menuB++;
}
if (oldMenuItem) {
menuHideItem(oldMenuItem);
@ -319,6 +330,7 @@ exports.addInteractive = function(menu, options) {
delete Bangle.CLKINFO_FOCUS;
menuHideItem(menu[options.menuA].items[options.menuB]);
exports.loadCount--;
delete exports.clockInfos[options.index];
};
options.redraw = function() {
drawItem(menu[options.menuA].items[options.menuB]);
@ -339,8 +351,7 @@ exports.addInteractive = function(menu, options) {
menuShowItem(menu[options.menuA].items[options.menuB]);
return true;
}
};
delete settings; // don't keep settings in RAM - save space
return options;
};

View File

@ -1,7 +1,7 @@
{ "id": "clock_info",
"name": "Clock Info Module",
"shortName": "Clock Info",
"version":"0.05",
"version":"0.06",
"description": "A library used by clocks to provide extra information on the clock face (Altitude, BPM, etc)",
"icon": "app.png",
"type": "module",

View File

@ -6,3 +6,4 @@
0.06: Add button for force compass calibration
0.07: Use 360-heading to output the correct heading value (fix #1866)
0.08: Added adjustment for Bangle.js magnetometer heading fix
0.09: use falling edge of button to reset compass (allows exit without compass reset)

View File

@ -68,7 +68,7 @@ g.clear(1);
g.setFont("6x8").setFontAlign(0,0,3).drawString(/*LANG*/"RESET", g.getWidth()-5, g.getHeight()/2);
setWatch(function() {
Bangle.resetCompass();
}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true});
}, (process.env.HWVERSION==2) ? BTN1 : BTN2, {repeat:true, edge:"falling"});
Bangle.loadWidgets();
Bangle.drawWidgets();

View File

@ -1,7 +1,7 @@
{
"id": "compass",
"name": "Compass",
"version": "0.08",
"version": "0.09",
"description": "Simple compass that points North",
"icon": "compass.png",
"screenshots": [{"url":"screenshot_compass.png"}],

View File

@ -15,5 +15,6 @@
{"name":"contourclock.settings.js","url":"contourclock.settings.js"},
{"name":"contourclock","url":"lib.js"},
{"name":"contourclock.img","url":"app-icon.js","evaluate":true}
]
],
"data": [{"name":"contourclock.json"}]
}

View File

@ -13,5 +13,6 @@
{"name":"cprassist.app.js","url":"cprassist.js"},
{"name":"cprassist.img","url":"cprassist-icon.js","evaluate":true},
{"name":"cprassist.settings.js","url":"settings.js"}
]
],
"data":[{"name":"cprassist.settings.json"}]
}

View File

@ -12,5 +12,8 @@
{"name":"cscsensor.app.js","url":"cscsensor.app.js"},
{"name":"cscsensor.settings.js","url":"settings.js"},
{"name":"cscsensor.img","url":"cscsensor-icon.js","evaluate":true}
],
"data": [
{"name":"cscsensor.json"}
]
}

View File

@ -13,5 +13,8 @@
{"name":"cycling.settings.js","url":"settings.js"},
{"name":"blecsc","url":"blecsc.js"},
{"name":"cycling.img","url":"cycling.icon.js","evaluate": true}
],
"data": [
{"name":"cycling.json"}
]
}

View File

@ -6,10 +6,13 @@
"type":"textinput",
"tags": "keyboard",
"supports" : ["BANGLEJS2"],
"screenshots": [{"url":"screenshot.png"}],
"screenshots": [{"url":"screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"textinput","url":"lib.js"},
{"name":"dragboard.settings.js","url":"settings.js"}
],
"data": [
{"name":"dragboard.json"}
]
}

View File

@ -6,10 +6,13 @@
"type":"textinput",
"tags": "keyboard",
"supports" : ["BANGLEJS2"],
"screenshots": [{"url":"screenshot.png"}],
"screenshots": [{"url":"screenshot.png"}],
"readme": "README.md",
"storage": [
{"name":"textinput","url":"lib.js"},
{"name":"draguboard.settings.js","url":"settings.js"}
],
"data": [
{"name":"draguboard.json"}
]
}

View File

@ -12,5 +12,6 @@
{"name":"f9lander.app.js","url":"app.js"},
{"name":"f9lander.img","url":"app-icon.js","evaluate":true},
{"name":"f9lander.settings.js", "url":"settings.js"}
]
],
"data":[{"name":"f9settings.json"}]
}

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: Allow redirection of loads to the launcher
0.03: Allow hiding the fastloading info screen
0.04: (WIP) Allow use of app history when going back (`load()` or `Bangle.load()` calls without specified app).

View File

@ -8,9 +8,16 @@ This allows fast loading of all apps with two conditions:
## Settings
* Activate app history and navigate back through recent apps instead of immediately loading the clock face
* If Quick Launch is installed it can be excluded from app history
* Allows to redirect all loads usually loading the clock to the launcher instead
* The "Fastloading..." screen can be switched off
## App history
* Long press of hardware button clears the app history and loads the clock face
* Installing the 'Fast Reset' app allows doing fastloads directly to the clock face by pressing the hardware button for one second. Useful if there are many apps in the history and the user want to access the clock quickly.
## Technical infos
This is still experimental but it uses the same mechanism as `.bootcde` does.
@ -19,3 +26,6 @@ It checks the app to be loaded for widget use and stores the result of that and
# Creator
[halemmerich](https://github.com/halemmerich)
# Contributors
[thyttan](https://github.com/thyttan)

View File

@ -1,5 +1,6 @@
{
const SETTINGS = require("Storage").readJSON("fastload.json") || {};
const s = require("Storage");
const SETTINGS = s.readJSON("fastload.json") || {};
let loadingScreen = function(){
g.reset();
@ -16,26 +17,26 @@ let loadingScreen = function(){
g.flip(true);
};
let cache = require("Storage").readJSON("fastload.cache") || {};
let cache = s.readJSON("fastload.cache") || {};
let checkApp = function(n){
// no widgets, no problem
if (!global.WIDGETS) return true;
let app = require("Storage").read(n);
let app = s.read(n);
if (cache[n] && E.CRC32(app) == cache[n].crc)
return cache[n].fast
return cache[n].fast;
cache[n] = {};
cache[n].fast = app.includes("Bangle.loadWidgets");
cache[n].crc = E.CRC32(app);
require("Storage").writeJSON("fastload.cache", cache);
s.writeJSON("fastload.cache", cache);
return cache[n].fast;
}
};
global._load = load;
let slowload = function(n){
global._load(n);
}
};
let fastload = function(n){
if (!n || checkApp(n)){
@ -50,17 +51,40 @@ let fastload = function(n){
};
global.load = fastload;
let appHistory, resetHistory, recordHistory;
if (SETTINGS.useAppHistory){
appHistory = s.readJSON("fastload.history.json",true)||[];
resetHistory = ()=>{appHistory=[];s.writeJSON("fastload.history.json",appHistory);};
recordHistory = ()=>{s.writeJSON("fastload.history.json",appHistory);};
}
Bangle.load = (o => (name) => {
if (Bangle.uiRemove && !SETTINGS.hideLoading) loadingScreen();
if (SETTINGS.useAppHistory){
if (name && name!=".bootcde" && !(name=="quicklaunch.app.js" && SETTINGS.disregardQuicklaunch)) {
// store the name of the app to launch
appHistory.push(name);
} else if (name==".bootcde") { // when Bangle.showClock is called
resetHistory();
} else if (name=="quicklaunch.app.js" && SETTINGS.disregardQuicklaunch) {
// do nothing with history
} else {
// go back in history
appHistory.pop();
name = appHistory[appHistory.length-1];
}
}
if (SETTINGS.autoloadLauncher && !name){
let orig = Bangle.load;
Bangle.load = (n)=>{
Bangle.load = orig;
fastload(n);
}
};
Bangle.showLauncher();
Bangle.load = orig;
} else
} else
o(name);
})(Bangle.load);
if (SETTINGS.useAppHistory) E.on('kill', ()=>{if (!BTN.read()) recordHistory(); else resetHistory();}); // Usually record history, but reset it if long press of HW button was used.
}

View File

@ -1,7 +1,7 @@
{ "id": "fastload",
"name": "Fastload Utils",
"shortName" : "Fastload Utils",
"version": "0.03",
"version": "0.04",
"icon": "icon.png",
"description": "Enable experimental fastloading for more apps",
"type":"bootloader",

View File

@ -1,7 +1,8 @@
(function(back) {
var FILE="fastload.json";
var settings;
var isQuicklaunchPresent = !!require('Storage').read("quicklaunch.app.js", 0, 1);
function writeSettings(key, value) {
var s = require('Storage').readJSON(FILE, true) || {};
s[key] = value;
@ -12,25 +13,52 @@
function readSettings(){
settings = require('Storage').readJSON(FILE, true) || {};
}
readSettings();
function buildMainMenu(){
var mainmenu = {
'': { 'title': 'Fastload', back: back },
'Force load to launcher': {
var mainmenu = {};
mainmenu[''] = { 'title': 'Fastload', back: back };
mainmenu['Activate app history'] = {
value: !!settings.useAppHistory,
onchange: v => {
writeSettings("useAppHistory",v);
if (v && settings.autoloadLauncher) {
writeSettings("autoloadLauncher",!v); // Don't use app history and load to launcher together.
setTimeout(()=>E.showMenu(buildMainMenu()), 0); // Update the menu so it can be seen if a value was automatically set to false (app history vs load launcher).
}
}
};
if (isQuicklaunchPresent) {
mainmenu['Exclude Quick Launch from history'] = {
value: !!settings.disregardQuicklaunch,
onchange: v => {
writeSettings("disregardQuicklaunch",v);
}
};
}
mainmenu['Force load to launcher'] = {
value: !!settings.autoloadLauncher,
onchange: v => {
writeSettings("autoloadLauncher",v);
if (v && settings.useAppHistory) {
writeSettings("useAppHistory",!v);
setTimeout(()=>E.showMenu(buildMainMenu()), 0); // Update the menu so it can be seen if a value was automatically set to false (app history vs load launcher).
} // Don't use app history and load to launcher together.
}
},
'Hide "Fastloading..."': {
};
mainmenu['Hide "Fastloading..."'] = {
value: !!settings.hideLoading,
onchange: v => {
writeSettings("hideLoading",v);
}
}
};
};
return mainmenu;
}

1
apps/fastreset/ChangeLog Normal file
View File

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

32
apps/fastreset/README.md Normal file
View File

@ -0,0 +1,32 @@
# Fast Reset
Reset the watch by holding the hardware button for half a second. If 'Fastload Utils' is installed this will typically be done with fastloading. A buzz acts as indicator.
Fast Reset was developed with the app history feature of 'Fastload Utils' in mind. If many apps are in the history stack, the user may want a fast way to exit directly to the clock face without using the firmwares reset function.
## Usage
Just install and it will run as boot code.
## Features
If 'Fastload Utils' is installed fastloading will be used when possible. Otherwise a standard `load(.bootcde)` is used.
If the hardware button is held for longer the standard reset functionality of the firmware is executed as well (total 1.5 seconds). And eventually the watchdog will be kicked.
## Controls
Hold the hardware button for half a second to feel the buzz, loading the clock face.
## Requests
Mention @[thyttan](https://github.com/thyttan) in an issue to the official [BangleApps repository](https://github.com/espruino/BangleApps/issues) for feature requests and bug reports.
## Acknowledgements
<a target="_blank" href="https://icons8.com/icon/15165/rewind">Rewind</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a>
## Creator
[thyttan](https://github.com/thyttan)

BIN
apps/fastreset/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 343 B

5
apps/fastreset/boot.js Normal file
View File

@ -0,0 +1,5 @@
{let buzzTimeout;
setWatch((e)=>{
if (e.state) buzzTimeout = setTimeout(()=>{Bangle.buzz(80,0.40);Bangle.showClock();}, 500);
if (!e.state && buzzTimeout) clearTimeout(buzzTimeout);},
BTN,{repeat:true, edge:'both' });}

View File

@ -0,0 +1,14 @@
{ "id": "fastreset",
"name": "Fast Reset",
"shortName":"Fast Reset",
"version":"0.01",
"description": "Reset the watch by holding the hardware button for half a second. If 'Fastload Utils' is installed this will typically be done with fastloading. A buzz acts as indicator.",
"icon": "app.png",
"type": "bootloader",
"tags": "system",
"supports" : ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"fastreset.boot.js","url":"boot.js"}
]
}

View File

@ -14,5 +14,6 @@
{"name":"game1024.app.js","url":"app.js"},
{"name":"game1024.settings.js","url":"settings.js"},
{"name":"game1024.img","url":"app-icon.js","evaluate":true}
]
],
"data":[{"name":"game1024.settings.json"}]
}

1
apps/gassist/ChangeLog Normal file
View File

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

11
apps/gassist/app.js Normal file
View File

@ -0,0 +1,11 @@
Bluetooth.println("");
Bluetooth.println(JSON.stringify({
t:"intent",
target:"activity",
action:"android.intent.action.VOICE_COMMAND",
flags:["FLAG_ACTIVITY_NEW_TASK"]
}));
setTimeout(function() {
Bangle.showClock();
}, 0);

BIN
apps/gassist/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

21
apps/gassist/boot.js Normal file
View File

@ -0,0 +1,21 @@
// load settings
var settings = Object.assign({
enableTap: true
}, require("Storage").readJSON("gassist.json", true) || {});
if (settings.enableTap) {
Bangle.on("tap", function(e) {
if (e.dir=="front" && e.double) {
Bluetooth.println("");
Bluetooth.println(JSON.stringify({
t:"intent",
target:"activity",
action:"android.intent.action.VOICE_COMMAND",
flags:["FLAG_ACTIVITY_NEW_TASK"]
}));
}
});
}
// clear variable
settings = undefined;

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwxH+AH4A/AH4ALiAAFFtoxmFpQxjFxwwfFyAwdFyQwcF9wuUGDQvuFywwYF/4vUnAABF9YuCGBAv/F/6/PGC4bE3QACG5YvdFoYxSLzAvuFw4wjLxbCidhAvVGB4UFF7QxMCZAuaGJIRKF7oATFtoA/AEPMAAQttGNQuHGE4vuFxIwlF/4v/d/4vwGBAumGIwtpAH4A/AEIA=="))

View File

@ -0,0 +1,18 @@
{
"id": "gassist",
"name": "Google Assist",
"version": "0.01",
"description": "A simple way to initiate Google Assistant on Android. Intents must be enabled in Gadgetbridge.",
"icon": "app.png",
"type": "app",
"tags": "tool, voice, tasker",
"supports": ["BANGLEJS","BANGLEJS2"],
"allow_emulator": false,
"storage": [
{"name":"gassist.boot.js","url":"boot.js"},
{"name":"gassist.app.js","url":"app.js"},
{"name":"gassist.settings.js","url":"settings.js"},
{"name":"gassist.img","url":"icon.js","evaluate":true}
],
"data": [{"name":"gassist.json"}]
}

33
apps/gassist/settings.js Normal file
View File

@ -0,0 +1,33 @@
(function (back) {
let storage = require('Storage');
let file = "gassist.json";
// Load and set default settings
let appSettings = Object.assign({
enableTap : true
}, storage.readJSON(file, true) || {});
// Save settings to storage
function writeSettings() {
storage.writeJSON(file, appSettings);
}
function showMenu() {
E.showMenu({
"": {
"title": "Google Assist"
},
"< Back": () => back(),
'Front Tap:': {
value: (appSettings.enableTap === true),
format: v => v ? "On" : "Off",
onchange: v => {
appSettings.enableTap = v;
writeSettings();
}
},
});
}
// Initially show the menu
showMenu();
});

View File

@ -2,3 +2,4 @@
0.02: BTN2->launcher, use smaller text to allow "20:00" to fit on screen
0.03: Changed setWatch to Bangle.setUI
0.04: Tell clock widgets to hide.
0.05: Making geissclock work on Bangle.js 2 (but only animate when unlocked!)

View File

@ -1,4 +1,7 @@
var W = 79, H = 64;
// if screen is always on, only animate when unlocked
var isScreenAlwaysOn = process.env.BOARD=="BANGLEJS2";
/*var compiled = E.compiledC(`
// void transl(int, int, int )
int transl(unsigned char *map, unsigned char *imgfrom, unsigned char *imgto) {
@ -46,6 +49,7 @@ var map = new Uint8Array(W*H);
var pal = new Uint16Array(256);
var PALETTES = 3;
var MAPS = 6;
var animInterval;
// If we're missing any maps, compute them!
(function() {
@ -65,6 +69,8 @@ function randomPalette() {
var n = (0|Math.random()*200000) % PALETTES;
var p = new Uint8Array(pal.buffer);
p.set(require("Storage").readArrayBuffer("geissclk."+n+".pal"));
if (!g.theme.dark) // if not dark, invert colors
E.mapInPlace(pal,pal,x=>x^0xFFFF);
}
function randomMap() {
@ -93,7 +99,7 @@ var im = {
};
var lastSeconds = -1;
function iterate() { "ram"
function iterate(clearBuf) { "ram"
var d = new Date();
var time = require("locale").time(d,1);
var seconds = d.getSeconds().toString().padStart(2,0);
@ -108,27 +114,59 @@ function iterate() { "ram"
gfx.buffer = dataa.buffer;
}
var x,y,n,t = getTime()/10;
var amt = 100*Bangle.getAccel().diff;
for (var i=0;i<amt;i++) {
//x = Math.round((W/2) + 20*Math.sin(t));
//y = Math.round((H/2) + 20*Math.cos(t));
//t += 0.628;
x = 1+(Math.random()*(W-2))|0;
y = 1+(Math.random()*(H-2))|0;
dataa[x + y*W] = 240;
if (clearBuf) {
gfx.clear();
} else { // do geiss animation
var amt = 100*Bangle.getAccel().diff;
for (var i=0;i<amt;i++) {
//x = Math.round((W/2) + 20*Math.sin(t));
//y = Math.round((H/2) + 20*Math.cos(t));
//t += 0.628;
x = 1+(Math.random()*(W-2))|0;
y = 1+(Math.random()*(H-2))|0;
dataa[x + y*W] = 240;
}
compiled.transl(addrmap, addra, addrb);
}
compiled.transl(addrmap, addra, addrb);
x = 8;
gfx.setFont("5x9Numeric7Seg",2);
gfx.drawString(time, x, 20);
gfx.setFont("5x9Numeric7Seg");
gfx.drawString(seconds, x+55, 30);
if (!clearBuf) { // don't draw seconds if not animating
gfx.setFont("5x9Numeric7Seg");
gfx.drawString(seconds, x+55, 30);
}
// firmwares pre-2v09 wouldn't accelerate a 3x blit if it went right to the RHS - hence we're 79px not 80
g.drawImage(im,1,24,{scale:3});
if (g.getWidth()==176) // Bangle.js 2
g.drawImage(im,8,24,{scale:2});
else
g.drawImage(im,3,24,{scale:3});
}
if (isScreenAlwaysOn) {
Bangle.on('lock',function(on) {
if (animInterval) {
clearInterval(animInterval);
animInterval = undefined;
}
if (!on) { // not locked - animate!
randomMap();
randomPalette();
iterate();
animInterval = setInterval(iterate, 50);
} else {
iterate(true); // just clear
animInterval = setTimeout(function() {
iterate(true);
animInterval = setInterval(function() {
iterate(true);
}, 60000);
}, 60000 - (Date.now() % 60000));
}
});
}
Bangle.on('lcdPower',function(on) {
if (animInterval) {
@ -144,11 +182,15 @@ Bangle.on('lcdPower',function(on) {
});
// Show launcher when button pressed
Bangle.setUI("clock");g.clear();
Bangle.setUI("clock");
g.clear(1);
Bangle.loadWidgets();
Bangle.drawWidgets();
iterate();
animInterval = setInterval(iterate, 50);
iterate(true);
if (Bangle.isLCDOn() && (!isScreenAlwaysOn || !Bangle.isLocked())) {
console.log("Starting");
animInterval = setInterval(iterate, 50);
}

View File

@ -1,12 +1,12 @@
{
"id": "geissclk",
"name": "Geiss Clock",
"version": "0.04",
"version": "0.05",
"description": "7 segment clock with animated background in the style of Ryan Geiss' music visualisation. NOTE: The first run will take ~1 minute to do some precalculation",
"icon": "clock.png",
"type": "clock",
"tags": "clock",
"supports": ["BANGLEJS"],
"supports": ["BANGLEJS","BANGLEJS2"],
"storage": [
{"name":"geissclk.app.js","url":"clock.js"},
{"name":"geissclk.precompute.js","url":"precompute.js"},

View File

@ -1,6 +1,6 @@
// PALETTES ---------------------------
E.showMessage("Precomputing\npalettes\n\nPlease wait...\n0 / 3");
(function() { // fire
(function() { "jit" // fire
for (var i=0;i<256;i++) {
var r = Math.min(i*6,240);
var g = Math.min(i*3,240);
@ -10,7 +10,7 @@ E.showMessage("Precomputing\npalettes\n\nPlease wait...\n0 / 3");
})()
require("Storage").write("geissclk.0.pal",pal.buffer);
E.showMessage("Precomputing\npalettes\n\nPlease wait...\n1 / 3");
(function() { // gunge
(function() { "jit" // gunge
for (var i=0;i<256;i++) {
var r = 0;
var g = Math.min(i*3,255);
@ -20,7 +20,7 @@ E.showMessage("Precomputing\npalettes\n\nPlease wait...\n1 / 3");
})()
require("Storage").write("geissclk.1.pal",pal.buffer);
E.showMessage("Precomputing\npalettes\n\nPlease wait...\n2 / 3");
(function() { // rainbow
(function() { "jit" // rainbow
for (var i=0;i<256;i++) {
var cl = E.HSBtoRGB((48+i)/128,1,Math.min(i/16,0.9),true);
var r = cl[0];
@ -35,7 +35,7 @@ require("Storage").write("geissclk.2.pal",pal.buffer);
// MAPS ----------------------------------------------
E.showMessage("Precomputing\nmaps\n\nPlease wait...\n0 / 5");
// straight out
(function() { "ram"; var n = 0; for (var y=0;y<H;y++) {
(function() { "jit"; var n = 0; for (var y=0;y<H;y++) {
for (var x=0;x<W;x++) {
var dx = x-(W/2);
var dy = y-(H/2);
@ -49,7 +49,7 @@ E.showMessage("Precomputing\nmaps\n\nPlease wait...\n0 / 5");
require("Storage").write("geissclk.0.map",map);
E.showMessage("Precomputing\nmaps\n\nPlease wait...\n1 / 5");
// ripple out
(function() { "ram"; var n = 0; for (var y=0;y<H;y++) {
(function() { "jit"; var n = 0; for (var y=0;y<H;y++) {
for (var x=0;x<W;x++) {
var dx = x-(W/2);
var dy = y-(H/2);
@ -63,7 +63,7 @@ E.showMessage("Precomputing\nmaps\n\nPlease wait...\n1 / 5");
require("Storage").write("geissclk.1.map",map);
E.showMessage("Precomputing\nmaps\n\nPlease wait...\n2 / 5");
// twisty outwards
(function() { "ram"; var n = 0; for (var y=0;y<H;y++) {
(function() { "jit"; var n = 0; for (var y=0;y<H;y++) {
for (var x=0;x<W;x++) {
var dx = x-(W/2);
var dy = y-(H/2);
@ -76,7 +76,7 @@ E.showMessage("Precomputing\nmaps\n\nPlease wait...\n2 / 5");
require("Storage").write("geissclk.2.map",map);
E.showMessage("Precomputing\nmaps\n\nPlease wait...\n3 / 5");
// spiral
(function() { "ram"; var n = 0; for (var y=0;y<H;y++) {
(function() { "jit"; var n = 0; for (var y=0;y<H;y++) {
for (var x=0;x<W;x++) {
var dx = x-(W/2);
var dy = y-(H/2);
@ -89,7 +89,7 @@ E.showMessage("Precomputing\nmaps\n\nPlease wait...\n3 / 5");
require("Storage").write("geissclk.3.map",map);
E.showMessage("Precomputing\nmaps\n\nPlease wait...\n4 / 5");
// blur down
(function() { "ram"; var n=0; for (var y=0;y<H;y++) {
(function() { "jit"; var n=0; for (var y=0;y<H;y++) {
for (var x=0;x<W;x++) {
map[n++] = 136 - 6*16 + (y&1)*8-4;
}
@ -97,7 +97,7 @@ E.showMessage("Precomputing\nmaps\n\nPlease wait...\n4 / 5");
require("Storage").write("geissclk.4.map",map);
E.showMessage("Precomputing\nmaps\n\nPlease wait...\n5 / 5");
// twisty
(function() { "ram"; var n=0; for (var y=0;y<H;y++) {
(function() { "jit"; var n=0; for (var y=0;y<H;y++) {
for (var x=0;x<W;x++) {
dx = Math.sin(y*0.2);
dy = Math.cos(x*0.2);

View File

@ -14,5 +14,6 @@
{"name":"getup.app.js","url":"app.js"},
{"name":"getup.settings.js","url":"settings.js"},
{"name":"getup.img","url":"app-icon.js","evaluate":true}
]
],
"data":[{"name":"getup.settings.json"}]
}

View File

@ -20,5 +20,6 @@
{"name":"happyclk.app.js","url":"happyclk.app.js"},
{"name":"happyclk.img","url":"happyclk.icon.js","evaluate":true},
{"name":"happyclk.settings.js","url":"happyclk.settings.js"}
]
],
"data":[{"name":"happyclk.setting.json"}]
}

View File

@ -4,7 +4,7 @@
"shortName":"HR Alarm",
"version":"0.02",
"description": "This invisible widget vibrates whenever the heart rate gets close to the upper limit or goes over or under the configured limits",
"icon": "widget.png",
"icon": "widget.png",
"type": "widget",
"tags": "widget",
"supports" : ["BANGLEJS2"],
@ -12,5 +12,8 @@
"storage": [
{"name":"hralarm.wid.js","url":"widget.js"},
{"name":"hralarm.settings.js","url":"settings.js"}
],
"data": [
{"name":"hralarm.json"}
]
}

View File

@ -15,5 +15,8 @@
{"name":"imageclock.app.js","url":"app.js"},
{"name":"imageclock.settings.js","url":"settings.js"},
{"name":"imageclock.img","url":"app-icon.js","evaluate":true}
],
"data": [
{"name":"imageclock.json"}
]
}

View File

@ -12,5 +12,6 @@
{"name":"textinput","url":"lib.js"},
{"name":"kbtouch.settings.js","url":"settings.js"}
],
"sortorder":-1
"sortorder":-1,
"data":[{"name":"kbtouch.settings.json"}]
}

View File

@ -24,4 +24,6 @@
0.24: Add ability to disable alarm functionality.
0.25: Add more colors to the settings and add the ability to disable the data charts+Markup.
0.26: Use widget_utils.
0.27: Report latest HRM rather than HRM 10 minutes ago (fix #2395)
0.27: Report latest HRM rather than HRM 10 minutes ago (fix #2395)
0.28: Battery Vref implemented correctly.
0.29: Support fastload.

View File

@ -25,7 +25,8 @@ the "sched" app must be installed on your device.
## Data that can be configured
* Steps - Steps loaded via the wpedom app.
* Battery - Current battery level in %
* VREF - Voltage of battery
* BattVolt - Voltage of battery
* VREF - Internal Voltage Reference
* HRM - Last measured HRM
* Temp - Weather temperature loaded via the weather module + gadgetbridge
* Humidity - Humidity loaded via the weather module + gadgetbridge

View File

@ -1,7 +1,8 @@
{
const TIMER_IDX = "lcars";
const SETTINGS_FILE = "lcars.setting.json";
const locale = require('locale');
const storage = require('Storage')
const storage = require('Storage');
const widget_utils = require('widget_utils');
let settings = {
alarm: -1,
@ -18,7 +19,7 @@ let settings = {
};
let saved_settings = storage.readJSON(SETTINGS_FILE, 1) || settings;
for (const key in saved_settings) {
settings[key] = saved_settings[key]
settings[key] = saved_settings[key];
}
/*
@ -36,10 +37,10 @@ let cGrey = "#424242";
*/
let lcarsViewPos = 0;
// let hrmValue = 0;
var plotMonth = false;
let plotMonth = false;
function convert24to16(input)
let convert24to16 = function(input)
{
let RGB888 = parseInt(input.replace(/^#/, ''), 16);
let r = (RGB888 & 0xFF0000) >> 16;
@ -55,17 +56,17 @@ function convert24to16(input)
RGB565 = RGB565 | b;
return "0x"+RGB565.toString(16);
}
};
var color1C = convert24to16(color1);//Converting colors to the correct format.
var color2C = convert24to16(color2);
var color3C = convert24to16(color3);
let color1C = convert24to16(color1);//Converting colors to the correct format.
let color2C = convert24to16(color2);
let color3C = convert24to16(color3);
/*
* Requirements and globals
*/
var colorPalette = new Uint16Array([//Used to change the color of the image if the user selects a color that is diffrent than the default.
let colorPalette = new Uint16Array([//Used to change the color of the image if the user selects a color that is diffrent than the default.
0x0000, // not used
color2C, // second
color3C, // third
@ -84,73 +85,73 @@ var colorPalette = new Uint16Array([//Used to change the color of the image if t
0x0000 // not used
],0,1);
var bgLeftFullscreen = {
let bgLeftFullscreen = {
width : 27, height : 176, bpp : 3,
transparent : 0,
buffer : require("heatshrink").decompress((atob("/4AB+VJkmSAQV///+BAtJn//5IIFkmf/4IGyVP/gIGpMnF41PHIImGF4ImHJoQmGJoIdK8hNHNY47C/JNGBIJZGyYJBQA5GCKH5Q/KAQAoUP7y/KH5QGDoQAy0hGF34JB6RGFr4JB9JkFl4JB+gdFy4JB/QdFpYJB/odFkqrCS4xGCWoyDCKH5Q1GShlJChQLCCg5TCHw5TMAD35FAoIIkgJB8hGGv/8Mg8/+QIFp4cB5IRGBIIvI/4IFybyCF4wTCDp5NBHZZiGz4JBLJKAGk4JBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4"))),
palette: colorPalette
};
var bgLeftNotFullscreen = {
let bgLeftNotFullscreen = {
width : 27, height : 152, bpp : 3,
transparent : 0,
buffer : require("heatshrink").decompress((atob("/4AB+VJkmSAQV///+BAtJn//5IIFkmf/4IGyVP/gIGpMnF41PHIImGF4ImHJoQmGJoIdK8hNHNY47C/JNGBIJZGyYJBQA5GCKH5Q/KAQAy0hGF34JB6RGFr4JB9JkFl4JB+gdFy4JB/QdFpYJB/odFkqrCS4xGCWoyhCKH5Q1GShlJChQLCCg5TCHw5TMAD35FAoIIkgJB8hGGv/8Mg8/+QIFp4cB5IRGBIIvI/4IFybyCF4wTCDp5NBHZZiGz4JBLJKAGk4JBO4xQ/KGQA8UP7y/KH5QnAHih/eX5Q/GQ4JCGRJlKCgxTDBAwgCCg5TCHwxTCNA4A=="))),
palette: colorPalette
};
var bgRightFullscreen = {
let bgRightFullscreen = {
width : 27, height : 176, bpp : 3,
transparent : 0,
buffer : require("heatshrink").decompress((atob("yVJkgCCyf/AAPJBAYCBk4JB8gUFyVP//yBAoCB//5BAwUCAAIUHAAIgGChopGv5TIn5TIz4yLKYxxC/iGI/xxGKH5Q/agwAnUP7y/KH4yGeVYAJ0hGF34JB6RGFr4JB9JkFl4JB+gdFy4JB/QdFpYJB/odFkp4CS4xGCWoyhCKH5QuDoxQCDpI7GDoJZGHYIRGLIQvGO4QvGMQRNJADv+GIqTC/5PGz4JBJ41JBIPJCg2TD4QLGn4JB/gUaHwRTGHwRTHBIRTGNAQyJ8gyI+QdFp4JB/IdFk5lLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJoyJC/4A=="))),
palette: colorPalette
};
var bgRightNotFullscreen = {
let bgRightNotFullscreen = {
width : 27, height : 152, bpp : 3,
transparent : 0,
buffer : require("heatshrink").decompress((atob("yVJkgCCyf/AAPJBAYCBk4JB8gUFyVP//yBAoCB//5BAwUCAAIUHAAIgGChopGv5TIn5TIz4yLKYxxC/iGI/xxGKH5Q/agwAx0hGF34JB6RGFr4JB9JkFl4JB+gdFy4JB/QdFpYJB/odFkqrCS4xGCWoyhCKH5QuDoxQCDpI7GDoJZGHYIRGLIQvGO4QvGMQRNJADv+GIqTC/5PGz4JBJ41JBIPJCg2TD4QLGn4JB/gUaHwRTGHwRTHBIRTGNAQyJ8gyI+QdFp4JB/IdFk5lLKH5QvAHih/eX5Q/KE4A8UP7y/KH5QGDpg7HJoxZCCIx3CJowmCF4yACJoyJC/4A="))),
palette: colorPalette
};
var bgLeft = settings.fullscreen ? bgLeftFullscreen : bgLeftNotFullscreen;
var bgRight= settings.fullscreen ? bgRightFullscreen : bgRightNotFullscreen;
let bgLeft = settings.fullscreen ? bgLeftFullscreen : bgLeftNotFullscreen;
let bgRight= settings.fullscreen ? bgRightFullscreen : bgRightNotFullscreen;
var iconEarth = {
let iconEarth = {
width : 50, height : 50, bpp : 3,
buffer : require("heatshrink").decompress(atob("AFtx48ECBsDwU5k/yhARLjgjBjlzAQMQEZcIkOP/fn31IEZgCBnlz58cEpM4geugEgwU/8+WNZJHDuHHvgmBCQ8goEOnVgJoMnyV58mACItHI4X8uAFBuVHnnz4BuGxk4////Egz3IkmWvPgNw8f/prB//BghTC+AjE7848eMjNnzySBwUJkmf/BuGuPDAQIjBiPHhhTCSQnjMo0ITANJn44Dg8MuFBggCCiFBcAJ0Bv5xEh+ITo2OhHkyf/OIQdBWwVHhgjBNwUE+fP/5EEgePMoYLBhMgyVJk/+BQQdC688I4XxOIc8v//NAvr+QEBj/5NwKVBy1/QYUciPBhk1EAJrC+KeC489QYaMBgU/8BNB9+ChEjz1Jkn/QYMBDQIgCcYTCCiP/nlzJQmenMAgV4//uy/9wRaB/1J8iVCcAfHjt9TYYICnhKCgRKBw159/v//r927OIeeoASBDQccvv3791KYVDBYPLJQeCnPnz//AAP6ocEjEkXgMgJQtz79fLAP8KYkccAcJ8Gf/f/xu/cAMQ4eP5MlyQRCMolx40YsOGBAPfnnzU4KVDpKMBvz8Dh0/8me7IICgkxJQXPIgZTD58sEgcJk+eNoONnFBhk4/5uB/pcDg5KD+4mEv4CBXISVDhEn31/8/+mH7x//JQK5CAAMB4JBCnnxJQf/+fJEgkAa4L+CAQOOjMn/1bXIRxDJQXx58f//Hhlz/88EgsChMgz/Zs/+nfkyV/8huDOI6SD498NwoACi1Z8+S/Plz17/+QCI7jC+ZxBmfPnojIAAMDcYWSp//2wRJEwq2GABECjMgNYwAmA="))
};
var iconSaturn = {
let iconSaturn = {
width : 50, height : 50, bpp : 3,
transparent : 1,
buffer : require("heatshrink").decompress(atob("AH4A/AEkQuPHCJ0ChEAwARNjAjBjgjOhs06Q2OEYVx4ARMhEggUMkANIDoIgBoEEgEBNxJEC6ZrBAAMwNxAjDNYcHNxIjB7dtEwIHBwRoKj158+cuPEjlwCRAjC23bpu0wRNDAAsHEYWeEwaSJ6YjCAQUNSRQjEzxQBWZMNEYlsmg2JWAIjCz95SoJuJggjDtuw6dMG5JKCz998wFBJRVNEYW0yaVBJRNhJQN9+4pCzhKJmBKC4YpB/fINxIgCzFxSoQ3J4ENm3CAQPb98wbpEcAQMYWwKYBNxMDXgc2/fv3g2IEAOAgAjBjy5CEhEMfYICBgfPnjdLjj+CgMHiC3JknDhhoINw4jCAB0IJQIANR4QjPAH4A/AFA"))
};
var iconMoon = {
let iconMoon = {
width : 50, height : 50, bpp : 3,
transparent : 1,
buffer : require("heatshrink").decompress(atob("AH4AQjlx44CCCZsg8eOkHDwAQKEYgmPhEgEQM48AOIgMHEYoCB4ATI8UAmH/x04JoRuJsImHuBKLn37EwZuIgEQOI8cEpXj/yYBhE8+YNGgkYoJxITBUPnAaC///nC+FjBuIOJZEB8YeCh/8AoYACoMEEAnEjhQDPQJKJ/DCDAoi5DoLdHAoMQgLjFWYPOnngh02IwXzwDjEgPGEYS8BI4MBYoSVG4fP/nghkAgZrDkngJQqSG4gvBg4sBQgkImHihEAWwP8ZBMBEYl5/+cSoVAGQIUFh04weJn///0gj/OEw5KEz45BzhuCTYQAEgePB4IACAoJuBnAQEa4XHjxKB//xFgWHJQsCRgMDEonipwjENwUBDQNx8+evvn/hTDLw3igE+EgZxB8UOXIvEJQUfEYOfv53DEQkgga5BJQvzx84cAj+CDoNh8/eEYJKDuCSEcocnEon+/7xEgFBIIcfB4Mf/IICXI2DgDdBAAn758gCIq5Dv4zBvJuIOIfjEgvP/ARHgwdCB4P3AoTdFAAk4EYk8SQgAFTALaDSQwAGh08//vnDmBABYmEEZYAzA=="))
};
var iconMars = {
let iconMars = {
width : 50, height : 50, bpp : 3,
transparent : 1,
buffer : require("heatshrink").decompress(atob("AH4ATjlwCJ+Dh0wwAQMg0cuPHjFhCZkDps0yVJkmQCBMEjFx42atOmzQmLhMkEYQCCCREQoOGEYmmzB0IEY4CBkARGoJKBEYQCEzgSGkGSpAjDyYCCphuGiFhJQgCD8ASFgRHGAQKbB6BuHJRGeOIsINxEk6dNmARDgMEjQjHAQPnVQojIyZKB6YSDNwK5FAQt54BuDXJIjBEwK5EgxKKXgq5BJRdgXIojJAQJKMcAM0EwM2JUApDoCVFExa7FkGCgAmIkAREEwUEjAmHCIgABhEggQmFpACBCIojBEwRQCzVhwkQU4YADgQmBwQCCI4IFBCAojFAQojGJQQjDAQgRGEZICBEo4gFyUIkilFJQUYEAZrBAQMYNw5KDSQSbCNwwABgOGEwgCBsPACQ5xGwdNnARJcAVh48evvnCJK8Chs+/fv33gCRcB48cuPHCBYA/ADAA=="))
};
var iconSatellite = {
let iconSatellite = {
width : 50, height : 50, bpp : 3,
transparent : 2,
buffer : require("heatshrink").decompress(atob("pMkyQC/ATGXhIRPyNl0gmPjlwCJ9ly1aCJ1c+fHJR1Hy1ZJR1I+fPnlx6QRLpe+/JKBr5KMuYjBJQMdCJce/fvJQW0CJUlEYQCBSpvvJQbXJjl0NwnzNxGQwEOnHhgF78+WqQyIrFx48cAQXz4ShJgAABh0+8cP//9LJEhg4jDuP3//0LhGQgYlBgeAn///5cIy8MuAmDCIP/9I4HkmCEYMOgHfCQWkCI0cuBuDgF/CIP+CI1Ny1IkeAgHANwIAB/QRFrj7BhkxEwQRC/4RFpbXDgSVBg4RCSorXDI4MJAQMfCIP8cwImDn37fwN58+kwHgLgSVFub7CI4NyBAJKDLgkuEYX78+evKtCLg0jEYRKC58JMoRcFkwjDJQTFDl65EkojEAQMdcwn/+gFC3YjEJQLXEpYRDWwQmEdI6SHAQO0CJUkx4jDF4gCIJQgRMXIjCEARIjCCJ2XEYPKCJqJBJQIROcAUpCJ0kybaDARtdCKAC2kAA="))
};
var iconCharging = {
let iconCharging = {
width : 50, height : 50, bpp : 3,
transparent : 5,
buffer : require("heatshrink").decompress(atob("23btugAwUBtoICARG0h048eODQYCJ6P/AAUCCJfbo4SDxYRLtEcuPHjlwgoRJ7RnIloUHoYjDAQfAExEAwUIkACEkSAIEYwCBhZKH6EIJI0CJRFHEY0BJRWBSgf//0AJRYSE4BKLj4SE8BKLv4RD/hK/JS2AXY0gXwRKG4cMmACCJQMAg8csEFJQsBAwfasEAm379u0gFbcBfHzgFBz1xMQZKBjY/D0E2+BOChu26yVEEYdww+cgAFCg+cgIfB6RKF4HbgEIkGChEAthfCJQ0eEAIjBBAMxk6GCJQtgtyVBwRKBAQMbHAJKGXIIFCgACBhl54qVG2E+EAJKBJoWAm0WJQ6SCXgdxFgMLJQvYjeAEAUwFIUitEtJQ14NwUHgEwKYZKGwOwNYX7XgWCg3CJQ5rB4MevPnAoPDJRJrCgEG/ECAoNsJRUwoEesIIBiJKI3CVDti/CJRKVDiJHBSo0YsOGjED8AjBcAcIgdhcAXAPIUAcAYIBcA4dBAQUG8BrBgBuCgOwcBEeXIK2BBAIFBgRqBGoYAChq8CcYUE4FbUYOACQsHzgjDgwFBCIImBAQsDtwYD7cAloRI22B86YBw5QBgoRJ7dAgYEDCJaeBJoMcsARMAQNoJIIRE6A"))
};
var iconWarning = {
let iconWarning = {
width : 50, height : 50, bpp : 3,
transparent : 1,
buffer : require("heatshrink").decompress(atob("kmSpIC/AWMyoQIFsmECJFJhMmA4QXByVICIwODAQ4RRFIQGD5JVLkIGDzJqMyAGDph8MiRKGyApEAoZKFyYIDQwMkSQNkQZABBhIIOOJRuEL5gRIAUKACVQMhmUSNYNDQYJTBBwYFByGTkOE5FJWYNMknCAQKYCiaSCpmGochDoSYBhMwTAZrChILBhmEzKPBF4ImBTAREBDoMmEwJVDoYjBycJFgWEJQRuLJQ1kmQCCjJlCBYbjCagaDBwyDBmBuBF4TjJAUQKINBChCDQxZBcZIIQF4NIgEAgKSDiQmEVQKMBoARBAAMCSQLLBVoxqKL4gaCChVCNwoRKOIo4CJIgABBoSMHpIRFgDdJOIJUBCAUJRgJuEAQb+DIIgRIAX4C/ASOQA"))
@ -172,26 +173,26 @@ Graphics.prototype.setFontAntonioLarge = function(scale) {
/*
* Draw watch face
*/
var drawTimeout;
function queueDraw() {
let drawTimeout;
let queueDraw = function() {
// Faster updates during alarm to ensure that it is
// shown correctly...
var timeout = isAlarmEnabled() ? 10000 : 60000;
let timeout = isAlarmEnabled() ? 10000 : 60000;
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
draw();
}, timeout - (Date.now() % timeout));
}
};
/**
* This function plots a data row in LCARS style.
* Note: It can be called async and therefore, the text alignment and
* font is set each time the function is called.
*/
function printRow(text, value, y, c){
let printRow = function(text, value, y, c){
g.setFontAntonioMedium();
g.setFontAlign(-1,-1,0);
@ -210,23 +211,23 @@ function printRow(text, value, y, c){
g.setColor(c);
g.setFontAlign(1,-1,0);
g.drawString(value, 126, y);
}
};
function drawData(key, y, c){
let drawData = function(key, y, c){
try{
_drawData(key, y, c);
} catch(ex){
// Show last error - next try hopefully works.
}
}
};
function _drawData(key, y, c){
let _drawData = function(key, y, c){
key = key.toUpperCase()
var text = key;
var value = "ERR";
var should_print= true;
let text = key;
let value = "ERR";
let should_print= true;
if(key == "STEPS"){
text = "STEP";
@ -239,21 +240,24 @@ function _drawData(key, y, c){
} else if (key == "VREF"){
value = E.getAnalogVRef().toFixed(2) + "V";
} else if (key =="BATTVOLT" ) {
text = "BATV";
value = (E.getAnalogVRef()*analogRead(3)*4).toFixed(2) + "V";
} else if(key == "HRM"){
value = Math.round(Bangle.getHealthStatus().bpm||Bangle.getHealthStatus("last").bpm);
} else if (key == "TEMP"){
var weather = getWeather();
let weather = getWeather();
value = weather.temp;
} else if (key == "HUMIDITY"){
text = "HUM";
var weather = getWeather();
let weather = getWeather();
value = weather.hum;
} else if (key == "WIND"){
text = "WND";
var weather = getWeather();
let weather = getWeather();
value = weather.wind;
} else if (key == "ALTITUDE"){
@ -277,18 +281,18 @@ function _drawData(key, y, c){
if(should_print){
printRow(text, value, y, c);
}
}
};
function drawHorizontalBgLine(color, x1, x2, y, h){
let drawHorizontalBgLine = function(color, x1, x2, y, h){
g.setColor(color);
for(var i=0; i<h; i++){
for(let i=0; i<h; i++){
g.drawLine(x1, y+i, x2,y+i);
}
}
};
function drawInfo(){
let drawInfo = function(){
if(lcarsViewPos != 0 || !settings.fullscreen){
return;
}
@ -310,9 +314,9 @@ function drawInfo(){
g.setColor(color3);
g.drawString("LOCK", 128, 53);
}
}
};
function drawState(){
let drawState = function(){
if(lcarsViewPos != 0){
return;
}
@ -322,11 +326,11 @@ function drawState(){
g.setFontAntonioMedium();
if(!isAlarmEnabled()){
var bat = E.getBattery();
var flash = storage.getFree() / process.env.STORAGE;
var current = new Date();
var hours = current.getHours();
var iconMsg =
let bat = E.getBattery();
let flash = storage.getFree() / process.env.STORAGE;
let current = new Date();
let hours = current.getHours();
let iconMsg =
Bangle.isCharging() ? { icon: iconCharging, text: "STATUS" } :
bat < 30 ? { icon: iconWarning, text: "BAT" } :
flash < 0.1 ? { icon: iconWarning, text: "DISK" } :
@ -348,12 +352,12 @@ function drawState(){
}
g.setFontAlign(-1, -1, 0);
}
};
function drawPosition0(){
let drawPosition0 = function(){
// Draw background image
var offset = settings.fullscreen ? 0 : 24;
let offset = settings.fullscreen ? 0 : 24;
g.drawImage(bgLeft, 0, offset);
drawHorizontalBgLine(color1, 25, 120, offset, 4);
drawHorizontalBgLine(color1, 130, 176, offset, 4);
@ -363,13 +367,13 @@ function drawPosition0(){
drawHorizontalBgLine(color2, 120, 176, 87, 4);
// The last line is a battery indicator too
var bat = E.getBattery() / 100.0;
var batStart = 19;
var batWidth = 172 - batStart;
var batX2 = parseInt(batWidth * bat + batStart);
let bat = E.getBattery() / 100.0;
let batStart = 19;
let batWidth = 172 - batStart;
let batX2 = parseInt(batWidth * bat + batStart);
drawHorizontalBgLine(color2, batStart, batX2, 171, 5);
drawHorizontalBgLine(cGrey, batX2, 172, 171, 5);
for(var i=0; i+batStart<=172; i+=parseInt(batWidth/4)){
for(let i=0; i+batStart<=172; i+=parseInt(batWidth/4)){
drawHorizontalBgLine(cBlack, batStart+i, batStart+i+3, 168, 8)
}
@ -379,8 +383,8 @@ function drawPosition0(){
// Write time
g.setFontAlign(-1, -1, 0);
g.setColor(cWhite);
var currentDate = new Date();
var timeStr = locale.time(currentDate,1);
let currentDate = new Date();
let timeStr = locale.time(currentDate,1);
g.setFontAntonioLarge();
if(settings.fullscreen){
g.drawString(timeStr, 27, 10);
@ -392,13 +396,13 @@ function drawPosition0(){
g.setColor(cWhite);
g.setFontAntonioMedium();
if(settings.fullscreen){
var dayStr = locale.dow(currentDate, true).toUpperCase();
let dayStr = locale.dow(currentDate, true).toUpperCase();
dayStr += " " + currentDate.getDate();
dayStr += " " + locale.month(currentDate, 1).toUpperCase();
g.drawString(dayStr, 30, 56);
} else {
var dayStr = locale.dow(currentDate, true).toUpperCase();
var date = currentDate.getDate();
let dayStr = locale.dow(currentDate, true).toUpperCase();
let date = currentDate.getDate();
g.drawString(dayStr, 128, 35);
g.drawString(date, 128, 55);
}
@ -412,11 +416,11 @@ function drawPosition0(){
// Draw state
drawState();
}
};
function drawPosition1(){
let drawPosition1 = function(){
// Draw background image
var offset = settings.fullscreen ? 0 : 24;
let offset = settings.fullscreen ? 0 : 24;
g.drawImage(bgRight, 149, offset);
if(settings.fullscreen){
drawHorizontalBgLine(color1, 0, 140, offset, 4);
@ -444,8 +448,8 @@ function drawPosition1(){
// Plot HRM graph
if(plotMonth){
var data = new Uint16Array(32);
var cnt = new Uint8Array(32);
let data = new Uint16Array(32);
let cnt = new Uint8Array(32);
health.readDailySummaries(new Date(), h=>{
data[h.day]+=h.bpm;
if (h.bpm) cnt[h.day]++;
@ -462,9 +466,9 @@ function drawPosition1(){
});
// Plot step graph
var data = new Uint16Array(32);
data = new Uint16Array(32);
health.readDailySummaries(new Date(), h=>data[h.day]+=h.steps/1000);
var gridY = parseInt(Math.max.apply(Math, data)/2);
let gridY = parseInt(Math.max.apply(Math, data)/2);
gridY = gridY <= 0 ? 1 : gridY;
require("graph").drawBar(g, data, {
axes : true,
@ -490,8 +494,8 @@ function drawPosition1(){
// Plot day
} else {
var data = new Uint16Array(24);
var cnt = new Uint8Array(24);
let data = new Uint16Array(24);
let cnt = new Uint8Array(24);
health.readDay(new Date(), h=>{
data[h.hr]+=h.bpm;
if (h.bpm) cnt[h.hr]++;
@ -508,9 +512,9 @@ function drawPosition1(){
});
// Plot step graph
var data = new Uint16Array(24);
data = new Uint16Array(24);
health.readDay(new Date(), h=>data[h.hr]+=h.steps);
var gridY = parseInt(Math.max.apply(Math, data)/1000)*1000;
let gridY = parseInt(Math.max.apply(Math, data)/1000)*1000;
gridY = gridY <= 0 ? 1000 : gridY;
require("graph").drawBar(g, data, {
axes : true,
@ -534,9 +538,9 @@ function drawPosition1(){
g.drawString("DAY", 154, 115);
}
}
}
};
function draw(){
let draw = function(){
// Queue draw first to ensure that its called in one minute again.
queueDraw();
@ -557,14 +561,14 @@ function draw(){
} else {
Bangle.drawWidgets();
}
}
};
/*
* Step counter via widget
*/
function getSteps() {
var steps = 0;
let getSteps = function() {
let steps = 0;
try{
if (WIDGETS.wpedom !== undefined) {
steps = WIDGETS.wpedom.getSteps();
@ -578,15 +582,15 @@ function getSteps() {
}
return steps;
}
};
function getWeather(){
var weatherJson;
let getWeather = function(){
let weatherJson;
try {
weatherJson = storage.readJSON('weather.json');
var weather = weatherJson.weather;
let weather = weatherJson.weather;
// Temperature
weather.temp = locale.temp(weather.temp-273.15);
@ -596,7 +600,7 @@ function getWeather(){
// Wind
const wind = locale.speed(weather.wind).match(/^(\D*\d*)(.*)$/);
var speedFactor = settings.speed == "kph" ? 1.0 : 1.0 / 1.60934;
let speedFactor = settings.speed == "kph" ? 1.0 : 1.0 / 1.60934;
weather.wind = Math.round(wind[1] * speedFactor);
return weather
@ -613,15 +617,15 @@ function getWeather(){
wdir: " ? ",
wrose: " ? "
};
}
};
/*
* Handle alarm
*/
function isAlarmEnabled(){
let isAlarmEnabled = function(){
try{
var alarm = require('sched');
var alarmObj = alarm.getAlarm(TIMER_IDX);
let alarm = require('sched');
let alarmObj = alarm.getAlarm(TIMER_IDX);
if(alarmObj===undefined || !alarmObj.on){
return false;
}
@ -630,35 +634,35 @@ function isAlarmEnabled(){
} catch(ex){ }
return false;
}
};
function getAlarmMinutes(){
let getAlarmMinutes = function(){
if(!isAlarmEnabled()){
return -1;
}
var alarm = require('sched');
var alarmObj = alarm.getAlarm(TIMER_IDX);
let alarm = require('sched');
let alarmObj = alarm.getAlarm(TIMER_IDX);
return Math.round(alarm.getTimeToAlarm(alarmObj)/(60*1000));
}
};
function increaseAlarm(){
let increaseAlarm = function(){
try{
var minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
var alarm = require('sched')
let minutes = isAlarmEnabled() ? getAlarmMinutes() : 0;
let alarm = require('sched')
alarm.setAlarm(TIMER_IDX, {
timer : (minutes+5)*60*1000,
});
alarm.reload();
} catch(ex){ }
}
};
function decreaseAlarm(){
let decreaseAlarm = function(){
try{
var minutes = getAlarmMinutes();
let minutes = getAlarmMinutes();
minutes -= 5;
var alarm = require('sched')
let alarm = require('sched')
alarm.setAlarm(TIMER_IDX, undefined);
if(minutes > 0){
@ -669,13 +673,13 @@ function decreaseAlarm(){
alarm.reload();
} catch(ex){ }
}
};
/*
* Listeners
*/
Bangle.on('lcdPower',on=>{
let onLcdPower = on=>{
if (on) {
// Whenever we connect to Gadgetbridge, reading data from
// health failed. Therefore, we update only partially...
@ -685,31 +689,34 @@ Bangle.on('lcdPower',on=>{
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = undefined;
}
});
};
Bangle.on('lcdPower', onLcdPower);
Bangle.on('lock', function(isLocked) {
let onLock = function(isLocked) {
drawInfo();
});
};
Bangle.on('lock', onLock);
Bangle.on('charging',function(charging) {
let onCharge = function(charging) {
drawState();
});
};
Bangle.on('charging', onCharge);
function feedback(){
let feedback = function(){
Bangle.buzz(40, 0.3);
}
};
// Touch gestures to control clock. We don't use swipe to be compatible with the bangle ecosystem
Bangle.on('touch', function(btn, e){
var left = parseInt(g.getWidth() * 0.2);
var right = g.getWidth() - left;
var upper = parseInt(g.getHeight() * 0.2);
var lower = g.getHeight() - upper;
let onTouch = function(btn, e){
let left = parseInt(g.getWidth() * 0.2);
let right = g.getWidth() - left;
let upper = parseInt(g.getHeight() * 0.2);
let 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;
let is_left = e.x < left;
let is_right = e.x > right;
let is_upper = e.y < upper;
let is_lower = e.y > lower;
if(!settings.disableData){
if(is_left && lcarsViewPos == 1){
@ -744,16 +751,30 @@ Bangle.on('touch', function(btn, e){
draw();
return;
}
});
};
// Touch gestures to control clock. We don't use swipe to be compatible with the bangle ecosystem
Bangle.on('touch', onTouch);
let themeBefore = g.theme;
/*
* Lets start widgets, listen for btn etc.
*/
// Show launcher when middle button pressed
Bangle.setUI("clock");
Bangle.setUI({mode:"clock",remove:function() {
if (drawTimeout) clearTimeout(drawTimeout);
delete Graphics.prototype.setFontAntonioMedium;
delete Graphics.prototype.setFontAntonioLarge;
Bangle.removeListener("lcdPower",onLcdPower);
Bangle.removeListener("lock",onLock);
Bangle.removeListener("charging",onCharge);
Bangle.removeListener("touch",onTouch);
require('sched').setAlarm(TIMER_IDX, undefined);
g.setTheme(themeBefore);
widget_utils.cleanup();
}});
Bangle.loadWidgets();
// Clear the screen once, at startup and draw clock
g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear();
draw();
}

View File

@ -26,7 +26,7 @@
}
var dataOptions = ["Steps", "Battery", "VREF", "HRM", "Temp", "Humidity", "Wind", "Altitude", "CoreT"];
var dataOptions = ["Steps", "Battery", "BattVolt", "VREF", "HRM", "Temp", "Humidity", "Wind", "Altitude", "CoreT"];
var speedOptions = ["kph", "mph"];
var color_options = ['Green','Orange','Cyan','Purple','Red','Blue','Yellow','White','Purple','Pink','Light Green','Dark Green'];
var bg_code = ['#00ff00','#FF9900','#0094FF','#FF00DC','#ff0000','#0000ff','#ffef00','#FFFFFF','#FF00FF','#6C00FF','#99FF00','#556B2F'];

View File

@ -3,7 +3,7 @@
"name": "LCARS Clock",
"shortName":"LCARS",
"icon": "lcars.png",
"version":"0.27",
"version":"0.29",
"readme": "README.md",
"supports": ["BANGLEJS2"],
"description": "Library Computer Access Retrieval System (LCARS) clock.",
@ -16,5 +16,6 @@
{"name":"lcars.app.js","url":"lcars.app.js"},
{"name":"lcars.img","url":"lcars.icon.js","evaluate":true},
{"name":"lcars.settings.js","url":"lcars.settings.js"}
]
],
"data":[{"name":"lcars.setting.json"}]
}

View File

@ -13,5 +13,8 @@
{"name":"limelight.app.js","url":"limelight.app.js"},
{"name":"limelight.settings.js","url":"limelight.settings.js"},
{"name":"limelight.img","url":"limelight.icon.js","evaluate":true}
],
"data": [
{"name":"limelight.json"}
]
}

View File

@ -14,5 +14,6 @@
{"name":"linuxclock.app.js","url":"app.js"},
{"name":"linuxclock.img","url":"app-icon.js","evaluate":true},
{"name":"linuxclock.settings.js","url":"settings.js"}
]
],
"data":[{"name":"linuxclock.setting.json"}]
}

View File

@ -10,5 +10,6 @@
"readme": "README.md",
"storage": [
{"name":"loadingscreen.settings.js","url":"settings.js"}
]
],
"data":[{"name":".loading"}]
}

View File

@ -96,4 +96,7 @@
Nav messages with '/' now get split on newlines
0.70: Handle nav messages from newer Gadgetbridge builds that output distance as a String
If we receive a 'music' message and we're in the messages app (but not showing a message) show music (#2814)
0.71: Cancel buzzing when watch unlocked, or when different messages viewed
0.71: Cancel buzzing when watch unlocked, or when different messages viewed
On 2v18.64+ firmware, 'No Messages' now has a 'back' button
0.72: Nav message updastes don't automatically launch navigation menu unless they're new
0.73: Add sharp left+right nav icons

View File

@ -70,6 +70,8 @@ var onMessagesModified = function(type,msg) {
if (msg.state && msg.state!="play") openMusic = false; // no longer playing music to go back to
if ((active!=undefined) && (active!="list") && (active!="music")) return; // don't open music over other screens (but do if we're in the main menu)
}
if (msg && msg.id=="nav" && msg.t=="modify" && active!="map")
return; // don't show an updated nav message if we're just in the menu
showMessage(msg&&msg.id);
};
Bangle.on("message", onMessagesModified);
@ -102,6 +104,8 @@ function showMapMessage(msg) {
case "right": img = "GhcBAABgAAA8AAAPgAAB8AAAPgAAB8D///j///9///+/AAPPAAHjgAD44AB8OAA+DgAPA4ABAOAAADgAAA4AAAOAAADgAAA4AAAOAAAA";break;
case "left_slight": img = "ERgB//B/+D/8H4AP4Af4A74Bz4Dj4HD4OD4cD4AD4ADwADwADgAHgAPAAOAAcAA4ABwADgAH";break;
case "right_slight": img = "ERgBB/+D/8H/4APwA/gD/APuA+cD44Phw+Dj4HPgAeAB4ADgAPAAeAA4ABwADgAHAAOAAcAA";break;
case "left_sharp": img = "GBaBAAAA+AAB/AAH/gAPjgAeBwA8BwB4B+DwB+HgB+PAB+eAB+8AB+4AB/wAB/gAB//gB//gB//gBwAABwAABwAABwAABw=="; break;
case "right_sharp": img = "GBaBAB8AAD+AAH/gAHHwAOB4AOA8AOAeAOAPB+AHh+ADx+AB5+AA9+AAd+AAP+AAH+AH/+AH/+AH/+AAAOAAAOAAAOAAAA==";break;
case "keep_left": img = "ERmBAACAAOAB+AD+AP+B/+H3+PO+8c8w4wBwADgAHgAPAAfAAfAAfAAfAAeAAeAAcAA8AA4ABwADgA==";break;
case "keep_right": img = "ERmBAACAAOAA/AD+AP+A//D/fPueeceY4YBwADgAPAAeAB8AHwAfAB8ADwAPAAcAB4ADgAHAAOAAAA==";break;
case "uturn_left": img = "GRiBAAAH4AAP/AAP/wAPj8APAfAPAHgHgB4DgA8BwAOA4AHAcADsOMB/HPA7zvgd9/gOf/gHH/gDh/gBwfgA4DgAcBgAOAAAHAAADgAABw==";break;
@ -414,8 +418,9 @@ function checkMessages(options) {
if (!options.clockIfNoMsg) return E.showPrompt(/*LANG*/"No Messages",{
title:/*LANG*/"Messages",
img:require("heatshrink").decompress(atob("kkk4UBrkc/4AC/tEqtACQkBqtUDg0VqAIGgoZFDYQIIM1sD1QAD4AIBhnqA4WrmAIBhc6BAWs8AIBhXOBAWz0AIC2YIC5wID1gkB1c6BAYFBEQPqBAYXBEQOqBAnDAIQaEnkAngaEEAPDFgo+IKA5iIOhCGIAFb7RqAIGgtUBA0VqobFgNVA")),
buttons : {/*LANG*/"Ok":1}
}).then(() => { load() });
buttons : {/*LANG*/"Ok":1},
back: () => load()
}).then(() => load());
return load();
}
// we have >0 messages

View File

@ -2,7 +2,7 @@
"id": "messagegui",
"name": "Message UI",
"shortName": "Messages",
"version": "0.71",
"version": "0.73",
"description": "Default app to display notifications from iOS and Gadgetbridge/Android",
"icon": "app.png",
"type": "app",

View File

@ -1,6 +1,6 @@
# Messages library
This library handles the passing of messages. It can storess a list of messages
This library handles the passing of messages. It can stores a list of messages
and allows them to be retrieved by other apps.
## Example
@ -37,18 +37,10 @@ myMessageListener = Bangle.on("message", (type, message)=>{
});
```
Apps can launch the full GUI by calling `require("messages").openGUI()`, if you
want to write your own GUI, it should include boot code that listens for
`"messageGUI"` events:
```js
Bangle.on("messageGUI", message=>{
if (message.handled) return; // another app already opened it's GUI
message.handled = true; // prevent other apps form launching
Bangle.load("my_message_gui.app.js");
})
```
Apps can launch the currently installed Message GUI by calling `require("messages").openGUI()`.
If you want to write your own GUI, it should include a library called `messagegui`
with a method called `open` that will cause it to be opened, with the
optionally supplied message. See `apps/messagegui/lib.js` for an example.
## Requests

View File

@ -107,7 +107,7 @@ exports.dismiss = function(msg) {
};
/**
* Emit a "type=openGUI" event, to open GUI app
* Open the Messages GUI app
*
* @param {object} [msg={}] Message the app should show
*/
@ -215,7 +215,7 @@ exports.buzz = function(msgSrc) {
let repeat = msgSettings.repeat;
if (repeat===undefined) repeat = 4; // repeat may be zero
if (repeat)
if (repeat)
{
exports.buzzInterval = setInterval(() => require("buzz").pattern(pattern), repeat*1000);
let vibrateTimeout = msgSettings.vibrateTimeout;

View File

@ -13,5 +13,6 @@
{"name":"metronome.app.js","url":"metronome.js"},
{"name":"metronome.img","url":"metronome-icon.js","evaluate":true},
{"name":"metronome.settings.js","url":"settings.js"}
]
],
"data":[{"name":"metronome.settings.json"}]
}

View File

@ -1,3 +1,4 @@
0.01: Initial version
0.02: Update for time_utils module
0.03: Use default Bangle formatter for booleans
0.04: Remove copied sched alarm.js & import newer features (oneshot alarms)

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