Merge remote-tracking branch 'upstream/master'

pull/2436/head
Hugh Barney 2022-12-23 23:44:25 +00:00
commit cfa3264da1
35 changed files with 393 additions and 328 deletions

View File

@ -1,3 +1,9 @@
0.01: New App!
0.02: Support Bangle.js 2
0.03: Fix bug for Bangle.js 2 where g.flip was not being called.
0.04: Combine code for both apps
Better colors for Bangle.js 2
Fix selection animation for Bangle.js 2
New icon
Slightly wider arc segments for better visibility
Extract arc drawing code in library

View File

@ -11,16 +11,21 @@ the players seated in a circle, set the number of segments equal to the number
of players, ensure that each person knows which colour represents them, and then
choose a segment. After a short animation, the chosen segment will fill the screen.
You can use Choozi to randomly select an element from any set with 2 to 13 members,
You can use Choozi to randomly select an element from any set with 2 to 15 members,
as long as you can define a bijection between members of the set and coloured
segments on the Bangle.js display.
## Controls
## Controls Bangle 1
BTN1: increase the number of segments
BTN2: choose a segment at random
BTN3: decrease the number of segments
## Controls Bangle 2
Swipe up/down: increase/decrease the number of segments
BTN1 or tap: choose a segment at random
## Creator
James Stanley

View File

@ -1 +1 @@
require("heatshrink").decompress(atob("mEwggLIrnM4uqAAIhPgvMAAPFzIABzWgCxkMCweqC4QABDBYtC5QVFDBoWCCo5KLOQIWKDARFICxhJIFwOpC5owFFyAwGUYIuOGAwuRC4guSJAgXBCyIwDIyQXF5IXSzJeVMAReUAAOQhheTMAVcC6yOUC4aOUC7GZUyoXXzWqhQXVxGqC9mYC7OqC9eoxEKC6uBC6uIwAXBPCSmBwEAC6Z2BiAXBJCR2BgEAjQXSlGBC4JgSLwYABJCJGBLwJIDGB+IIwRIDGByNBIwZIDGBhdBRoQwSLoIuFGAYYKCwIuGGAgYI1QWBRgYYJMYmaFoSMEAAyrBAAgVCCxgYGjAWQAAMBC4UILZQA=="))
require("heatshrink").decompress(atob("mEwwcH/4AW/u27dt2wQL/YOBCIXbv4QI+AODAQVsh4RHwEbCI0LCI9gCIOANAXbsFbG437tkDPg1btoRFFoILBgmSpMggECHQO/CAf2CIVJkgRBAQIjC24RFsECCItIgIRFMYMAiQRFpMAlqmDVwPYgAOEAQUggu274RD4BWCCIskCIPbCIPt20ABwwCCwARFgIRJyEWCIVt2EJCJi2BCJmSUgIRCwARNt/7CIIOICI1sWAwCFoFbCOtt8EACJsAgARR8hwBCJlJk4RlgARQAgIRKDwMn/gRBdJgRPyARBn4RBpARLiQRB/4RBgIRJwAREpIRLAYP///ypMgCJMACI0ECI4JCp4RB/wZECIsAAYN/CIP/5JPDCIhjDCIraHTIWTCAX//K7DCI+fCIf/EZA1CCAn//ipCLIsBk4RF/5ZHCIIQG//wPo8vCI//6QRFpYQIAAPpCIeXCBQAC/VfBI4="))

View File

@ -4,15 +4,16 @@
*
* James Stanley 2021
*/
var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
const GU = require("graphics_utils");
var colours = ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#00ffff', '#ff00ff', '#ffffff'];
var colours2 = ['#808080', '#404040', '#000040', '#004000', '#400000', '#ff8000', '#804000', '#4000c0'];
var stepAngle = 0.18; // radians - resolution of polygon
var gapAngle = 0.035; // radians - gap between segments
var perimMin = 110; // px - min. radius of perimeter
var perimMax = 120; // px - max. radius of perimeter
var perimMin = g.getWidth()*0.40; // px - min. radius of perimeter
var perimMax = g.getWidth()*0.49; // px - max. radius of perimeter
var segmentMax = 106; // px - max radius of filled-in segment
var segmentMax = g.getWidth()*0.38; // px - max radius of filled-in segment
var segmentStep = 5; // px - step size of segment fill animation
var circleStep = 4; // px - step size of circle fill animation
@ -22,10 +23,10 @@ var minSpeed = 0.001; // rad/sec
var animStartSteps = 300; // how many steps before it can start slowing?
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
var ballSize = 3; // px - ball radius
var ballTrack = 100; // px - radius of ball path
var ballTrack = perimMin - ballSize*2; // px - radius of ball path
var centreX = 120; // px - centre of screen
var centreY = 120; // px - centre of screen
var centreX = g.getWidth()*0.5; // px - centre of screen
var centreY = g.getWidth()*0.5; // px - centre of screen
var fontSize = 50; // px
@ -33,7 +34,6 @@ var radians = 2*Math.PI; // radians per circle
var defaultN = 3; // default value for N
var minN = 2;
var maxN = colours.length;
var N;
var arclen;
@ -51,42 +51,14 @@ function shuffle (array) {
}
}
// draw an arc between radii minR and maxR, and between
// angles minAngle and maxAngle
function arc(minR, maxR, minAngle, maxAngle) {
var step = stepAngle;
var angle = minAngle;
var inside = [];
var outside = [];
var c, s;
while (angle < maxAngle) {
c = Math.cos(angle);
s = Math.sin(angle);
inside.push(centreX+c*minR); // x
inside.push(centreY+s*minR); // y
// outside coordinates are built up in reverse order
outside.unshift(centreY+s*maxR); // y
outside.unshift(centreX+c*maxR); // x
angle += step;
}
c = Math.cos(maxAngle);
s = Math.sin(maxAngle);
inside.push(centreX+c*minR);
inside.push(centreY+s*minR);
outside.unshift(centreY+s*maxR);
outside.unshift(centreX+c*maxR);
var vertices = inside.concat(outside);
g.fillPoly(vertices, true);
}
// draw the arc segments around the perimeter
function drawPerimeter() {
g.setBgColor('#000000');
g.clear();
for (var i = 0; i < N; i++) {
g.setColor(colours[i%colours.length]);
var minAngle = (i/N)*radians;
arc(perimMin,perimMax,minAngle,minAngle+arclen);
GU.fillArc(g, centreX, centreY, perimMin,perimMax,minAngle,minAngle+arclen, stepAngle);
}
}
@ -131,6 +103,7 @@ function animateChoice(target) {
g.fillCircle(x, y, ballSize);
oldx=x;
oldy=y;
if (process.env.HWVERSION == 2) g.flip();
}
}
@ -141,11 +114,15 @@ function choose() {
var maxAngle = minAngle + arclen;
animateChoice((minAngle+maxAngle)/2);
g.setColor(colours[chosen%colours.length]);
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
arc(i, perimMax, minAngle, maxAngle);
arc(0, perimMax, minAngle, maxAngle);
for (var r = 1; r < segmentMax; r += circleStep)
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep){
GU.fillArc(g, centreX, centreY, i, perimMax, minAngle, maxAngle, stepAngle);
if (process.env.HWVERSION == 2) g.flip();
}
GU.fillArc(g, centreX, centreY, 0, perimMax, minAngle, maxAngle, stepAngle);
for (var r = 1; r < segmentMax; r += circleStep){
g.fillCircle(centreX,centreY,r);
if (process.env.HWVERSION == 2) g.flip();
}
g.fillCircle(centreX,centreY,segmentMax);
}
@ -171,38 +148,47 @@ function setN(n) {
drawPerimeter();
}
// save N to choozi.txt
// save N to choozi.save
function writeN() {
var file = require("Storage").open("choozi.txt","w");
file.write(N);
var savedN = read();
if (savedN != N) require("Storage").write("choozi.save","" + N);
}
// load N from choozi.txt
function read(){
var n = require("Storage").read("choozi.save");
if (n !== undefined) return parseInt(n);
return defaultN;
}
// load N from choozi.save
function readN() {
var file = require("Storage").open("choozi.txt","r");
var n = file.readLine();
if (n !== undefined) setN(parseInt(n));
else setN(defaultN);
setN(read());
}
shuffle(colours); // is this really best?
Bangle.setLCDMode("direct");
Bangle.setLCDTimeout(0); // keep screen on
if (process.env.HWVERSION == 1){
colours=colours.concat(colours2);
shuffle(colours);
} else {
shuffle(colours);
shuffle(colours2);
colours=colours.concat(colours2);
}
var maxN = colours.length;
if (process.env.HWVERSION == 1){
Bangle.setLCDMode("direct");
Bangle.setLCDTimeout(0); // keep screen on
}
readN();
drawN();
setWatch(() => {
setN(N+1);
drawN();
}, BTN1, {repeat:true});
setWatch(() => {
writeN();
drawPerimeter();
choose();
}, BTN2, {repeat:true});
setWatch(() => {
setN(N-1);
drawN();
}, BTN3, {repeat:true});
Bangle.setUI("updown", (v)=>{
if (!v){
writeN();
drawPerimeter();
choose();
} else {
setN(N-v);
drawN();
}
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 551 B

View File

@ -1,207 +0,0 @@
/* Choozi - Choose people or things at random using Bangle.js.
* Inspired by the "Chwazi" Android app
*
* James Stanley 2021
*/
var colours = ['#ff0000', '#ff8080', '#00ff00', '#80ff80', '#0000ff', '#8080ff', '#ffff00', '#00ffff', '#ff00ff', '#ff8000', '#ff0080', '#8000ff', '#0080ff'];
var stepAngle = 0.18; // radians - resolution of polygon
var gapAngle = 0.035; // radians - gap between segments
var perimMin = 80; // px - min. radius of perimeter
var perimMax = 87; // px - max. radius of perimeter
var segmentMax = 70; // px - max radius of filled-in segment
var segmentStep = 5; // px - step size of segment fill animation
var circleStep = 4; // px - step size of circle fill animation
// rolling ball animation:
var maxSpeed = 0.08; // rad/sec
var minSpeed = 0.001; // rad/sec
var animStartSteps = 300; // how many steps before it can start slowing?
var accel = 0.0002; // rad/sec/sec - acc-/deceleration rate
var ballSize = 3; // px - ball radius
var ballTrack = 75; // px - radius of ball path
var centreX = 88; // px - centre of screen
var centreY = 88; // px - centre of screen
var fontSize = 50; // px
var radians = 2*Math.PI; // radians per circle
var defaultN = 3; // default value for N
var minN = 2;
var maxN = colours.length;
var N;
var arclen;
// https://www.frankmitchell.org/2015/01/fisher-yates/
function shuffle (array) {
var i = 0
, j = 0
, temp = null;
for (i = array.length - 1; i > 0; i -= 1) {
j = Math.floor(Math.random() * (i + 1));
temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
// draw an arc between radii minR and maxR, and between
// angles minAngle and maxAngle
function arc(minR, maxR, minAngle, maxAngle) {
var step = stepAngle;
var angle = minAngle;
var inside = [];
var outside = [];
var c, s;
while (angle < maxAngle) {
c = Math.cos(angle);
s = Math.sin(angle);
inside.push(centreX+c*minR); // x
inside.push(centreY+s*minR); // y
// outside coordinates are built up in reverse order
outside.unshift(centreY+s*maxR); // y
outside.unshift(centreX+c*maxR); // x
angle += step;
}
c = Math.cos(maxAngle);
s = Math.sin(maxAngle);
inside.push(centreX+c*minR);
inside.push(centreY+s*minR);
outside.unshift(centreY+s*maxR);
outside.unshift(centreX+c*maxR);
var vertices = inside.concat(outside);
g.fillPoly(vertices, true);
}
// draw the arc segments around the perimeter
function drawPerimeter() {
g.clear();
for (var i = 0; i < N; i++) {
g.setColor(colours[i%colours.length]);
var minAngle = (i/N)*radians;
arc(perimMin,perimMax,minAngle,minAngle+arclen);
}
}
// animate a ball rolling around and settling at "target" radians
function animateChoice(target) {
var angle = 0;
var speed = 0;
var oldx = -10;
var oldy = -10;
var decelFromAngle = -1;
var allowDecel = false;
for (var i = 0; true; i++) {
angle = angle + speed;
if (angle > radians) angle -= radians;
if (i < animStartSteps || (speed < maxSpeed && !allowDecel)) {
speed = speed + accel;
if (speed > maxSpeed) {
speed = maxSpeed;
/* when we reach max speed, we know how long it takes
* to accelerate, and therefore how long to decelerate, so
* we can work out what angle to start decelerating from */
if (decelFromAngle < 0) {
decelFromAngle = target-angle;
while (decelFromAngle < 0) decelFromAngle += radians;
while (decelFromAngle > radians) decelFromAngle -= radians;
}
}
} else {
if (!allowDecel && (angle < decelFromAngle) && (angle+speed >= decelFromAngle)) allowDecel = true;
if (allowDecel) speed = speed - accel;
if (speed < minSpeed) speed = minSpeed;
if (speed == minSpeed && angle < target && angle+speed >= target) return;
}
var r = i/2;
if (r > ballTrack) r = ballTrack;
var x = centreX+Math.cos(angle)*r;
var y = centreY+Math.sin(angle)*r;
g.setColor('#000000');
g.fillCircle(oldx,oldy,ballSize+1);
g.setColor('#ffffff');
g.fillCircle(x, y, ballSize);
oldx=x;
oldy=y;
g.flip();
}
}
// choose a winning segment and animate its selection
function choose() {
var chosen = Math.floor(Math.random()*N);
var minAngle = (chosen/N)*radians;
var maxAngle = minAngle + arclen;
animateChoice((minAngle+maxAngle)/2);
g.setColor(colours[chosen%colours.length]);
for (var i = segmentMax-segmentStep; i >= 0; i -= segmentStep)
arc(i, perimMax, minAngle, maxAngle);
arc(0, perimMax, minAngle, maxAngle);
for (var r = 1; r < segmentMax; r += circleStep)
g.fillCircle(centreX,centreY,r);
g.fillCircle(centreX,centreY,segmentMax);
}
// draw the current value of N in the middle of the screen, with
// up/down arrows
function drawN() {
g.setColor(g.theme.fg);
g.setFont("Vector",fontSize);
g.drawString(N,centreX-g.stringWidth(N)/2+4,centreY-fontSize/2);
if (N < maxN)
g.fillPoly([centreX-6,centreY-fontSize/2-7, centreX+6,centreY-fontSize/2-7, centreX, centreY-fontSize/2-14]);
if (N > minN)
g.fillPoly([centreX-6,centreY+fontSize/2+5, centreX+6,centreY+fontSize/2+5, centreX, centreY+fontSize/2+12]);
}
// update number of segments, with min/max limit, "arclen" update,
// and screen reset
function setN(n) {
N = n;
if (N < minN) N = minN;
if (N > maxN) N = maxN;
arclen = radians/N - gapAngle;
drawPerimeter();
}
// save N to choozi.txt
function writeN() {
var file = require("Storage").open("choozi.txt","w");
file.write(N);
}
// load N from choozi.txt
function readN() {
var file = require("Storage").open("choozi.txt","r");
var n = file.readLine();
if (n !== undefined) setN(parseInt(n));
else setN(defaultN);
}
shuffle(colours); // is this really best?
Bangle.setLCDTimeout(0); // keep screen on
readN();
drawN();
setWatch(() => {
writeN();
drawPerimeter();
choose();
}, BTN1, {repeat:true});
Bangle.on('touch', function(zone,e) {
if(e.x>+88){
setN(N-1);
drawN();
}else{
setN(N+1);
drawN();
}
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -1,7 +1,7 @@
{
"id": "choozi",
"name": "Choozi",
"version": "0.03",
"version": "0.04",
"description": "Choose people or things at random using Bangle.js.",
"icon": "app.png",
"tags": "tool",
@ -10,8 +10,10 @@
"allow_emulator": true,
"screenshots": [{"url":"bangle1-choozi-screenshot1.png"},{"url":"bangle1-choozi-screenshot2.png"}],
"storage": [
{"name":"choozi.app.js","url":"app.js","supports": ["BANGLEJS"]},
{"name":"choozi.app.js","url":"appb2.js","supports": ["BANGLEJS2"]},
{"name":"choozi.app.js","url":"app.js"},
{"name":"choozi.img","url":"app-icon.js","evaluate":true}
],
"data": [
{"name":"choozi.save"}
]
}

View File

@ -15,4 +15,5 @@
0.29: Use 'modules/suncalc.js' to avoid it being copied 8 times for different apps
0.30: BJS2: swipe seems to be working now
0.31: Tweaking the swipe option; Added mylocation as a dependency.
Remove calls to Bangle.loadWidgets as they are not needed and create warnings
Remove calls to Bangle.loadWidgets as they are not needed and create warnings
0.32: Added setting to show single timezone small, like where multiple ones are configured

View File

@ -9,6 +9,7 @@ All this is configurable:
- Show seconds only when unlocked (saves battery) / always / do not show seconds
- Green color on dark mode (on/off)
- 1 Offset Small: single location shows as small (like more than 1)
- Show sun info (on/off) (set your location in the mylocation app)
- Rotation degree on swipe (off / 90 / 180 / 270)

View File

@ -104,6 +104,7 @@ let def = function(value, def) {
let settings = require('Storage').readJSON(SETTINGSFILE, true) || {};
secondsMode = def(settings.secondsMode, "when unlocked");
showSunInfo = def(settings.showSunInfo, true);
singleOffsetSmall = def(settings.singleOffsetSmall, false);
colorWhenDark = def(settings.colorWhenDark, "green");
rotationTarget = def(settings.rotationTarget, "90");
rotationTarget = parseInt(rotationTarget) || 0;
@ -257,7 +258,7 @@ let draw = function() {
minutes = doublenum(dx.getMinutes());
if (offsets.length === 1) {
if (offsets.length === 1 && !singleOffsetSmall) {
let date = [require("locale").dow(new Date(), 1), require("locale").date(new Date(), 1)];
// For a single secondary timezone, draw it bigger and drop time zone to second line
const xOffset = 30;

View File

@ -2,7 +2,7 @@
"id": "hworldclock",
"name": "Hanks World Clock",
"shortName": "Hanks World Clock",
"version": "0.31",
"version": "0.32",
"description": "Current time zone plus up to three others",
"allow_emulator":true,
"icon": "app.png",

View File

@ -36,13 +36,20 @@
"< Back": () => back(),
"Seconds": stringInSettings("secondsMode", ["always", "when unlocked", "none"]),
"Color w. dark": stringInSettings("colorWhenDark", ["green", "default"]),
"Show SunInfo": {
value: (settings.showSunInfo !== undefined ? settings.showSunInfo : true),
onchange: v => {
settings.showSunInfo = v;
writeSettings();
}
},
"1 Offset Small": {
value: (settings.singleOffsetSmall !== undefined ? settings.singleOffsetSmall : false),
onchange: v=> {
settings.singleOffsetSmall = v;
writeSettings();
}
},
"Show SunInfo": {
value: (settings.showSunInfo !== undefined ? settings.showSunInfo : true),
onchange: v => {
settings.showSunInfo = v;
writeSettings();
}
},
"Rotation": stringInSettings("rotationTarget", ["off", "90", "180", "270"]),
};

1
apps/mitherm/ChangeLog Normal file
View File

@ -0,0 +1 @@
0.01: Create mitherm app with support for pvvx firmware only

22
apps/mitherm/README.md Normal file
View File

@ -0,0 +1,22 @@
Reads BLE advertisement data from Xiaomi temperature/humidity sensors running the
`pvvx` custom firmware (https://github.com/pvvx/ATC_MiThermometer).
## Features
* Display temperature
* Display humidity
* Display battery state of sensor
* Auto-refresh every 5 minutes
* Manual refresh on demand
* Add aliases for MAC addresses to easily recognise devices
## Planned features
* Supprt for other advertising formats:
* atc1441 format
* BTHome
* Xiaomi Mijia format
* Configurable auto-refresh interval
* Configurable scan length (currently 30s)
* Alerts when temperature outside defined limits (with a widget or bootcode to
work when app is inactive)

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

@ -0,0 +1 @@
require("heatshrink").decompress(atob("mEwwhC/AH4Ac5gWVhnM4AWVAAIYTCwQABCywYRIoYADJJwWHDB4RD5sz7hJPFIlP//0MRxFE6f/AAM9JJgWE4gWCAANMDBZcEn4XE+ZiKFwhcBCYPdDYRiEGAoXDLgf97vfMQwXILggXFMQYXHLgoXB6czMQoXHLgQXJMQQXG4YWEI44ABngXGh4XHF4v/+DAGC6DXGC5BHGC509F4IXTdwIABV4gXOIwIABJAoX/C6p3Xa4a/UABAXfgczABswC/4XmAH4A/ABY"))

172
apps/mitherm/app.js Normal file
View File

@ -0,0 +1,172 @@
var filterTemperature = [{
serviceData: {
"181a": {}
}
}];
var results = {};
var macs = [];
var aliases = require("Storage").readJSON("mitherm.json", true);
if (!aliases) aliases = {};
var lastSeen = {};
var current = 0;
var scanning = false;
var timeoutDraw;
var timeoutScan;
const scan = function() {
if (!scanning) { // Don't start scanning if already doing so.
scanning = true;
if (timeoutScan) clearTimeout(timeoutScan);
timeoutScan = setTimeout(scan, 300000); // Scan again in 5 minutes.
drawScanState(scanning);
NRF.findDevices(function(devices) {
onDevices(devices);
}, {
filters: filterTemperature,
timeout: 30000 // Scan for 30s
});
}
};
const onDevices = function(devices) {
let now = Date.now();
for (let i = 0; i < devices.length; i++) {
let device = devices[i];
let processedData = extractData(device.data);
console.log({
rssi: device.rssi,
data: processedData
});
if (!macs.includes(processedData.MAC)) {
macs.push(processedData.MAC);
}
results[processedData.MAC] = processedData;
lastSeen[processedData.MAC] = now;
}
console.log("Scan complete.");
scanning = false;
writeOutput();
};
const extractData = function(thedata) {
let data = DataView(thedata);
let MAC = [];
for (let i = 9; i > 3; i--) {
MAC.push(data.getUint8(i, true).toString(16).padStart(2, "0"));
}
out = {
size: data.getUint8(0, true),
uid: data.getUint8(1, true),
UUID: data.getUint16(2, true),
MAC: MAC.join(":"),
temperature: data.getInt16(10, true) * 0.01,
humidity: data.getUint16(12, true) * 0.01,
battery_mv: data.getUint16(14, true),
battery_level: data.getUint8(16, true),
};
return out;
};
const writeOutput = function() {
let now = Date.now();
if (timeoutDraw) clearTimeout(timeoutDraw);
timeoutDraw = setTimeout(writeOutput, 60000); // Refresh in 1 minute.
g.clear(true);
Bangle.drawWidgets();
g.reset();
drawScanState(scanning);
if (macs.length == 0) return;
processedData = results[macs[current]];
g.setFont12x20(2);
g.drawString(`${processedData.temperature.toFixed(2)}°C`, 10, 30);
g.drawString(`${processedData.humidity.toFixed(2)} %`, 10, 70);
g.setFont6x15();
g.drawString(`${((now - lastSeen[macs[current]]) / 60000).toFixed(0)} min ago`, 10, 130);
g.drawString(`${processedData.battery_level} % battery`, 80, 130);
g.drawString(` ${processedData.MAC in aliases ? aliases[processedData.MAC] : processedData.MAC}: ${current + 1} / ${macs.length}`, 10, 150);
};
const scrollDevices = function(directionLR) {
// Swipe left or right to move between devices.
current -= directionLR; // inverted feels a more familiar gesture.
if (current + 1 > macs.length)
current = 0;
if (current < 0)
current = macs.length - 1;
writeOutput();
};
const drawScanState = function(state) {
if (state)
g.fillRect(160, 160, 170, 170);
else
g.clearRect(160, 160, 170, 170);
};
const setAlias = function(mac, alias) {
if (alias === "") {
delete aliases[mac];
}
else {
aliases[mac] = alias;
require("Storage").writeJSON("mitherm.json", aliases);
}
};
const changeAlias = function(mac) {
g.clear();
require("textinput").input((mac in aliases) ? aliases[mac] : "").then(function(text) {
setAlias(mac, text);
setUI();
writeOutput();
});
};
const setUI = function() {
Bangle.setUI({
mode: "custom",
swipe: scrollDevices,
btn: function() {
E.showMenu(actionsMenu);
}
});
};
const actionsMenu = {
"": {
"title": "-- Actions --",
"back": function() {
E.showMenu();
},
"remove": function() {
setUI();
writeOutput();
},
},
"Scan now": function() {
scan();
E.showMenu();
},
"Edit alias": function() {
changeAlias(macs[current]);
},
};
setUI();
Bangle.loadWidgets();
g.setClipRect(Bangle.appRect);
scan();
writeOutput();

BIN
apps/mitherm/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 863 B

View File

@ -0,0 +1,15 @@
{
"id": "mitherm",
"name": "Xiaomi Mijia Temperature and Humidity display",
"shortName": "MiTherm",
"version": "0.01",
"description": "Reads and displays data from Xiaomi temperature/humidity sensors running custom firmware",
"icon": "app.png",
"tags": "xiaomi,mi,ble,bluetooth,thermometer,humidity",
"readme": "README.md",
"supports": ["BANGLEJS", "BANGLEJS2"],
"storage": [
{"name":"mitherm.app.js","url":"app.js"},
{"name":"mitherm.img","url":"app-icon.js","evaluate":true}
]
}

View File

@ -1,3 +1,4 @@
0.01: New App!
0.02: Reset font to save some memory during remove
0.03: Added support for locale based time
0.04: Stability improvements

View File

@ -1,7 +1,12 @@
{ // must be inside our own scope here so that when we are unloaded everything disappears
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
const fontBitmap = E.toString(require('heatshrink').decompress(atob('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')));
Graphics.prototype.setFontPaytoneOne = function(scale) {
// Actual height 81 (91 - 11)
this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('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'))),
fontBitmap,
46,
atob("ITZOMzs7SDxHNUdGIQ=="),
113+(scale<<8)+(1<<16)
@ -9,8 +14,6 @@ Graphics.prototype.setFontPaytoneOne = function(scale) {
return this;
};
{ // must be inside our own scope here so that when we are unloaded everything disappears
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
let drawTimeout;
let g2 = Graphics.createArrayBuffer(g.getWidth(),90,1,{msb:true});

View File

@ -1,12 +1,12 @@
{ "id": "slopeclock",
"name": "Slope Clock",
"version":"0.03",
"version":"0.04",
"description": "A clock where hours and minutes are divided by a sloping line. When the minute changes, the numbers slide off the screen",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],
"type": "clock",
"tags": "clock",
"supports" : ["BANGLEJS2"],
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"slopeclock.app.js","url":"app.js"},
{"name":"slopeclock.img","url":"app-icon.js","evaluate":true}

View File

@ -7,3 +7,5 @@
0.05: Images in clkinfo are optional now
0.06: Added support for locale based time
0.07: README file update as UI interaction was not easy to understand
0.08: Stability improvements - ensure we continue even if a flat string can't be allocated
Stop ClockInfo text drawing outside the allocated area

View File

@ -1,14 +1,3 @@
Graphics.prototype.setFontPaytoneOne = function(scale) {
// Actual height 71 (81 - 11)
this.setFontCustom(
E.toString(require('heatshrink').decompress(atob('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'))),
46,
atob("HTBFLTQ0PzU/Lz8+HQ=="),
100+(scale<<8)+(1<<16)
);
return this;
};
{ // must be inside our own scope here so that when we are unloaded everything disappears
// we also define functions using 'let fn = function() {..}' for the same reason. function decls are global
@ -16,6 +5,17 @@ let settings = Object.assign(
require("Storage").readJSON("slopeclockpp.default.json", true) || {},
require("Storage").readJSON("slopeclockpp.json", true) || {}
);
const fontBitmap = E.toString(require('heatshrink').decompress(atob('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')));
Graphics.prototype.setFontPaytoneOne = function(scale) {
// Actual height 71 (81 - 11)
this.setFontCustom(fontBitmap,
46,
atob("HTBFLTQ0PzU/Lz8+HQ=="),
100+(scale<<8)+(1<<16)
);
return this;
};
let drawTimeout;
@ -47,6 +47,15 @@ let bgColor = bgColors[(Math.random()*bgColors.length)|0]||"#000";
// Draw the hour, and the minute into an offscreen buffer
let draw = function() {
// queue next draw
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
animate(false, function() {
draw();
});
}, 60000 - (Date.now() % 60000));
// Now draw this one
R = Bangle.appRect;
x = R.w / 2;
y = R.y + R.h / 2 - 12; // 12 = room for date
@ -70,15 +79,6 @@ let draw = function() {
g2.setColor(0).fillPoly([0,0, g2.getWidth(),0, 0,slope*2]);
// start the animation *in*
animate(true);
// queue next draw
if (drawTimeout) clearTimeout(drawTimeout);
drawTimeout = setTimeout(function() {
drawTimeout = undefined;
animate(false, function() {
draw();
});
}, 60000 - (Date.now() % 60000));
};
let isAnimIn = true;
@ -123,7 +123,9 @@ let animate = function(isIn, callback) {
// clock info menus (scroll up/down for info)
let clockInfoDraw = (itm, info, options) => {
let texty = options.y+41;
g.reset().setFont("6x15").setBgColor(options.bg).setColor(options.fg).clearRect(options.x, texty-15, options.x+options.w-2, texty);
// set a cliprect to stop us drawing outside our box
g.reset().setClipRect(options.x, options.y, options.x+options.w-1, options.y+options.h-1);
g.setFont("6x15").setBgColor(options.bg).setColor(options.fg).clearRect(options.x, texty-15, options.x+options.w-2, texty);
if (options.focus) g.setColor(options.hl);
if (options.x < g.getWidth()/2) { // left align
@ -135,6 +137,8 @@ let clockInfoDraw = (itm, info, options) => {
if (info.img) g.clearRect(x-23, options.y, x, options.y+23).drawImage(info.img, x-23, options.y);
g.setFontAlign(1,1).drawString(info.text, x,texty);
}
// return ClipRect
g.setClipRect(0,0,g.getWidth()-1, g.getHeight()-1);
};
let clockInfoItems = require("clock_info").load();
let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { x:126, y:24, w:50, h:40, draw : clockInfoDraw, bg : g.theme.bg, fg : g.theme.fg, hl : "#f00"/*red*/ });

View File

@ -1,6 +1,6 @@
{ "id": "slopeclockpp",
"name": "Slope Clock ++",
"version":"0.07",
"version":"0.08",
"description": "A clock where hours and minutes are divided by a sloping line. When the minute changes, the numbers slide off the screen. This is a clone of the original Slope Clock which shows extra information and allows the colors to be selected.",
"icon": "app.png",
"screenshots": [{"url":"screenshot.png"}],

View File

@ -5,3 +5,4 @@
0.05: Set sortorder to -10 so that others can take -1 etc
0.06: Set sortorder to -10 in widget code
0.07: Remove check for .isLocked (extremely old firmwares), speed up widget loading
0.08: Don't completely remove the lock widget when screen unlocked (use 1px) to ensure appRect/drawWidgets still thinks there are widgets

View File

@ -1,7 +1,7 @@
{
"id": "widlock",
"name": "Lock Widget",
"version": "0.07",
"version": "0.08",
"description": "On devices with always-on display (Bangle.js 2) this displays lock icon whenever the display is locked",
"icon": "widget.png",
"type": "widget",

View File

@ -1,8 +1,8 @@
Bangle.on("lock", function() {
WIDGETS["lock"].width = Bangle.isLocked()?16:0;
WIDGETS["lock"].width = Bangle.isLocked()?16:1;
Bangle.drawWidgets();
});
WIDGETS["lock"]={area:"tl",sortorder:10,width:Bangle.isLocked()?16:0,draw:function(w) {
WIDGETS["lock"]={area:"tl",sortorder:10,width:Bangle.isLocked()?16:1,draw:function(w) {
if (Bangle.isLocked())
g.reset().drawImage(atob("DhABH+D/wwMMDDAwwMf/v//4f+H/h/8//P/z///f/g=="), w.x+1, w.y+4);
}};

View File

@ -1,4 +1,5 @@
0.01: Moved messages widget into standalone widget app
0.02: Fix 'srcs' being defined in global scope
Remove library stub
0.03: Fix messages not showing if UI auto-open is disabled
0.03: Fix messages not showing if UI auto-open is disabled
0.04: Now shows message icons again (#2416)

View File

@ -1,7 +1,7 @@
{
"id": "widmessages",
"name": "Message Widget",
"version": "0.03",
"version": "0.04",
"description": "Widget showing new messages",
"icon": "app.png",
"type": "widget",

View File

@ -22,10 +22,9 @@
let settings = Object.assign({flash: true, maxMessages: 3}, require("Storage").readJSON("messages.settings.json", true) || {});
if (recall!==true || settings.flash) {
const msgsShown = E.clip(this.srcs.length, 0, settings.maxMessages);
srcs = Object.keys(this.srcs);
g.reset().clearRect(this.x, this.y, this.x+this.width, this.y+23);
for(let i = 0; i<msgsShown; i++) {
const src = srcs[i];
const src = this.srcs[i];
const colors = [
g.theme.bg,
require("messageicons").getColor(src, {settings: settings})

View File

@ -49,7 +49,7 @@ function onFoundDeviceInfo(deviceId, deviceVersion) {
if (deviceId != "BANGLEJS" && deviceId != "BANGLEJS2") {
showToast(`You're using ${deviceId}, not a Bangle.js. Did you want <a href="https://espruino.com/apps">espruino.com/apps</a> instead?` ,"warning", 20000);
} else if (versionLess(deviceVersion, RECOMMENDED_VERSION)) {
showToast(`You're using an old Bangle.js firmware (${deviceVersion}) and ${RECOMMENDED_VERSION} is available (<a href="http://www.espruino.com/ChangeLog" target="_blank">see changes</a>). You can update ${fwExtraText}<a href="${fwURL}" target="_blank">with the instructions here</a>` ,"warning", 20000);
showToast(`You're using an old Bangle.js firmware (${deviceVersion}) and ${RECOMMENDED_VERSION} is available (<a href="https://www.espruino.com/ChangeLog" target="_blank">see changes</a>). You can update ${fwExtraText}<a href="${fwURL}" target="_blank">with the instructions here</a>` ,"warning", 20000);
}
// check against features shown?
filterAppsForDevice(deviceId);

35
modules/graphics_utils.js Normal file
View File

@ -0,0 +1,35 @@
// draw an arc between radii minR and maxR, and between angles minAngle and maxAngle centered at X,Y. All angles are radians.
exports.fillArc = function(graphics, X, Y, minR, maxR, minAngle, maxAngle, stepAngle) {
var step = stepAngle || 0.2;
var angle = minAngle;
var inside = [];
var outside = [];
var c, s;
while (angle < maxAngle) {
c = Math.cos(angle);
s = Math.sin(angle);
inside.push(X+c*minR); // x
inside.push(Y+s*minR); // y
// outside coordinates are built up in reverse order
outside.unshift(Y+s*maxR); // y
outside.unshift(X+c*maxR); // x
angle += step;
}
c = Math.cos(maxAngle);
s = Math.sin(maxAngle);
inside.push(X+c*minR);
inside.push(Y+s*minR);
outside.unshift(Y+s*maxR);
outside.unshift(X+c*maxR);
var vertices = inside.concat(outside);
graphics.fillPoly(vertices, true);
}
exports.degreesToRadians = function(degrees){
return Math.PI/180 * degrees;
}
exports.radiansToDegrees = function(radians){
return 180/Math.PI * degrees;
}

View File

@ -53,8 +53,14 @@ exports.cleanup = function() {
back onscreen with a downwards swipe. Use .show to undo.
First parameter controls automatic hiding time, 0 equals not hiding at all.
Default value is 2000ms until hiding.
Bangle.js 2 only at the moment. */
Bangle.js 2 only at the moment. On Bangle.js 1 widgets will be hidden permanently.
Note: On Bangle.js 1 is is possible to draw widgets in an offscreen area of the LCD
and use Bangle.setLCDOffset. However we can't detect a downward swipe so how to
actually make this work needs some thought.
*/
exports.swipeOn = function(autohide) {
if (process.env.HWVERSION!==2) return exports.hide();
exports.cleanup();
if (!global.WIDGETS) return;
exports.autohide=autohide===undefined?2000:autohide;