mirror of https://github.com/espruino/BangleApps
weather: Show Gadgetbridge weather reports
Based on http://forum.espruino.com/comments/15194626/, where NebbishHacker did most of the actual work :-)pull/340/head
parent
26d8855ea2
commit
319307cdd1
17
apps.json
17
apps.json
|
@ -348,6 +348,23 @@
|
||||||
{"name":"files.img","url":"files-icon.js","evaluate":true}
|
{"name":"files.img","url":"files-icon.js","evaluate":true}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{ "id": "weather",
|
||||||
|
"name": "Weather",
|
||||||
|
"icon": "icon.png",
|
||||||
|
"version":"0.01",
|
||||||
|
"description": "Show Gadgetbridge weather report",
|
||||||
|
"readme": "readme.md",
|
||||||
|
"tags": "widget,outdoors",
|
||||||
|
"storage": [
|
||||||
|
{"name":"weather.app.js","url":"app.js"},
|
||||||
|
{"name":"weather.wid.js","url":"widget.js"},
|
||||||
|
{"name":"weather","url":"lib.js"},
|
||||||
|
{"name":"weather.img","url":"icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [
|
||||||
|
{"name": "weather.json"}
|
||||||
|
]
|
||||||
|
},
|
||||||
{ "id": "widbat",
|
{ "id": "widbat",
|
||||||
"name": "Battery Level Widget",
|
"name": "Battery Level Widget",
|
||||||
"icon": "widget.png",
|
"icon": "widget.png",
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
(() => {
|
||||||
|
function draw(w) {
|
||||||
|
g.reset();
|
||||||
|
g.setColor(0).fillRect(0, 24, 239, 239);
|
||||||
|
|
||||||
|
require('weather').drawIcon(w.txt, 65, 90, 55);
|
||||||
|
const locale = require("locale");
|
||||||
|
|
||||||
|
g.setColor(-1);
|
||||||
|
|
||||||
|
const temp = locale.temp(w.temp).match(/^(\D*\d*)(.*)$/);
|
||||||
|
let width = g.setFont("Vector", 40).stringWidth(temp[1]);
|
||||||
|
width += g.setFont("Vector", 20).stringWidth(temp[2]);
|
||||||
|
g.setFont("Vector", 40).setFontAlign(-1, -1, 0);
|
||||||
|
g.drawString(temp[1], 180-width/2, 70);
|
||||||
|
g.setFont("Vector", 20).setFontAlign(1, -1, 0);
|
||||||
|
g.drawString(temp[2], 180+width/2, 70);
|
||||||
|
|
||||||
|
g.setFont("6x8", 1);
|
||||||
|
g.setFontAlign(-1, 0, 0);
|
||||||
|
g.drawString("Humidity", 135, 130);
|
||||||
|
g.drawString("Wind", 135, 142);
|
||||||
|
g.setFontAlign(1, 0, 0);
|
||||||
|
g.drawString(w.hum+"%", 225, 130);
|
||||||
|
g.drawString(locale.speed(w.wind), 225, 142);
|
||||||
|
|
||||||
|
g.setFont("6x8", 2).setFontAlign(0, 0, 0);
|
||||||
|
g.drawString(w.loc, 120, 170);
|
||||||
|
|
||||||
|
g.setFont("6x8", 1).setFontAlign(0, 0, 0);
|
||||||
|
g.drawString(w.txt.charAt(0).toUpperCase()+w.txt.slice(1), 120, 190);
|
||||||
|
|
||||||
|
g.flip();
|
||||||
|
}
|
||||||
|
|
||||||
|
const _GB = global.GB;
|
||||||
|
global.GB = (event) => {
|
||||||
|
if (event.t==="weather") draw(event);
|
||||||
|
if (_GB) setTimeout(_GB, 0, event);
|
||||||
|
};
|
||||||
|
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
const weather = require('weather').load();
|
||||||
|
if (weather) {
|
||||||
|
draw(weather);
|
||||||
|
} else {
|
||||||
|
E.showMessage('Weather unknown\n\nIs Gadgetbridge\nconnected?');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show launcher when middle button pressed
|
||||||
|
setWatch(Bangle.showLauncher, BTN2, {repeat: false, edge: 'falling'})
|
||||||
|
})()
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEwwhC/AE8N6AXV7vdFyoXBGCQUBAAoXp73u93tC6YWBAAIXSFwQwDRiAWDGASSBmYABIx5IDgYXCmC7KCoYRBnvUCwQwKC4gSEAAgwKC5gwKCwPjC6inBC6owBC6wVKPBXd6YXMDBAuNJJQXWfwZITC/6QIBw073ezR6m73anOJAwuHeBAYFIw4WJAAsL3YQOAAxeBCiWIC4e72AJChGACpMIxAXBIwIwEBIIXKBgouDEIYuLC4ghEC6ELLoYXPU4YXFLxQNBBgcLEQqqQFwYA/AH4AYA=="))
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,176 @@
|
||||||
|
exports = {
|
||||||
|
save: weather => {
|
||||||
|
let json = require('Storage').readJSON('weather.json')||{}
|
||||||
|
json.weather = Object.assign({}, weather) // don't mutate GB events
|
||||||
|
delete json.weather.t // don't save the event type (if present)
|
||||||
|
require('Storage').write('weather.json', json)
|
||||||
|
},
|
||||||
|
load: () => {
|
||||||
|
let json = require('Storage').readJSON('weather.json')||{}
|
||||||
|
return json.weather
|
||||||
|
},
|
||||||
|
drawIcon: (cond, x, y, r) => {
|
||||||
|
function drawSun(x, y, r) {
|
||||||
|
g.setColor("#FF7700");
|
||||||
|
g.fillCircle(x, y, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawCloud(x, y, r, c) {
|
||||||
|
const u = r/12;
|
||||||
|
if (c==null) c = "#EEEEEE";
|
||||||
|
g.setColor(c);
|
||||||
|
g.fillCircle(x-8*u, y+3*u, 4*u);
|
||||||
|
g.fillCircle(x-4*u, y-2*u, 5*u);
|
||||||
|
g.fillCircle(x+4*u, y+0*u, 4*u);
|
||||||
|
g.fillCircle(x+9*u, y+4*u, 3*u);
|
||||||
|
g.fillPoly([
|
||||||
|
x-8*u, y+7*u,
|
||||||
|
x-8*u, y+3*u,
|
||||||
|
x-4*u, y-2*u,
|
||||||
|
x+4*u, y+0*u,
|
||||||
|
x+9*u, y+4*u,
|
||||||
|
x+9*u, y+7*u,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawBrokenClouds(x, y, r) {
|
||||||
|
drawCloud(x+1/8*r, y-1/8*r, 7/8*r, "#777777");
|
||||||
|
drawCloud(x-1/8*r, y+1/8*r, 7/8*r);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawFewClouds(x, y, r) {
|
||||||
|
drawSun(x+3/8*r, y-1/8*r, 5/8*r);
|
||||||
|
drawCloud(x-1/8*r, y+1/8*r, 7/8*r);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawRainLines(x, y, r) {
|
||||||
|
g.setColor("#FFFFFF");
|
||||||
|
const y1 = y+1/2*r;
|
||||||
|
const y2 = y+1*r;
|
||||||
|
g.fillPoly([
|
||||||
|
x-6/12*r+1, y1,
|
||||||
|
x-8/12*r+1, y2,
|
||||||
|
x-7/12*r, y2,
|
||||||
|
x-5/12*r, y1,
|
||||||
|
]);
|
||||||
|
g.fillPoly([
|
||||||
|
x-2/12*r+1, y1,
|
||||||
|
x-4/12*r+1, y2,
|
||||||
|
x-3/12*r, y2,
|
||||||
|
x-1/12*r, y1,
|
||||||
|
]);
|
||||||
|
g.fillPoly([
|
||||||
|
x+2/12*r+1, y1,
|
||||||
|
x+0/12*r+1, y2,
|
||||||
|
x+1/12*r, y2,
|
||||||
|
x+3/12*r, y1,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawShowerRain(x, y, r) {
|
||||||
|
drawFewClouds(x, y-1/3*r, r);
|
||||||
|
drawRainLines(x, y, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawRain(x, y, r) {
|
||||||
|
drawBrokenClouds(x, y-1/3*r, r);
|
||||||
|
drawRainLines(x, y, r);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawThunderstorm(x, y, r) {
|
||||||
|
function drawLightning(x, y, r) {
|
||||||
|
g.setColor("#FF7700");
|
||||||
|
g.fillPoly([
|
||||||
|
x-2/6*r, y-r,
|
||||||
|
x-4/6*r, y+1/6*r,
|
||||||
|
x-1/6*r, y+1/6*r,
|
||||||
|
x-3/6*r, y+1*r,
|
||||||
|
x+3/6*r, y-1/6*r,
|
||||||
|
x+0/6*r, y-1/6*r,
|
||||||
|
x+3/6*r, y-r,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
drawBrokenClouds(x, y-1/3*r, r);
|
||||||
|
drawLightning(x-1/12*r, y+1/2*r, 1/2*r);
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawSnow(x, y, r) {
|
||||||
|
function rotatePoints(points, pivotX, pivotY, angle) {
|
||||||
|
for(let i = 0; i<points.length; i += 2) {
|
||||||
|
const x = points[i];
|
||||||
|
const y = points[i+1];
|
||||||
|
points[i] = Math.cos(angle)*(x-pivotX)-Math.sin(angle)*(y-pivotY)+
|
||||||
|
pivotX;
|
||||||
|
points[i+1] = Math.sin(angle)*(x-pivotX)+Math.cos(angle)*(y-pivotY)+
|
||||||
|
pivotY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g.setColor("#FFFFFF");
|
||||||
|
const w = 1/12*r;
|
||||||
|
for(let i = 0; i<=6; ++i) {
|
||||||
|
const points = [
|
||||||
|
x+w, y,
|
||||||
|
x-w, y,
|
||||||
|
x-w, y+r,
|
||||||
|
x+w, y+r,
|
||||||
|
];
|
||||||
|
rotatePoints(points, x, y, i/3*Math.PI);
|
||||||
|
g.fillPoly(points);
|
||||||
|
|
||||||
|
for(let j = -1; j<=1; j += 2) {
|
||||||
|
const points = [
|
||||||
|
x+w, y+7/12*r,
|
||||||
|
x-w, y+7/12*r,
|
||||||
|
x-w, y+r,
|
||||||
|
x+w, y+r,
|
||||||
|
];
|
||||||
|
rotatePoints(points, x, y+7/12*r, j/3*Math.PI);
|
||||||
|
rotatePoints(points, x, y, i/3*Math.PI);
|
||||||
|
g.fillPoly(points);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawMist(x, y, r) {
|
||||||
|
const layers = [
|
||||||
|
[-0.4, 0.5],
|
||||||
|
[-0.8, 0.3],
|
||||||
|
[-0.2, 0.9],
|
||||||
|
[-0.9, 0.7],
|
||||||
|
[-0.2, 0.3],
|
||||||
|
];
|
||||||
|
|
||||||
|
g.setColor("#FFFFFF");
|
||||||
|
for(let i = 0; i<5; ++i) {
|
||||||
|
g.fillRect(x+layers[i][0]*r, y+(0.4*i-0.9)*r, x+layers[i][1]*r,
|
||||||
|
y+(0.4*i-0.7)*r-1);
|
||||||
|
g.fillCircle(x+layers[i][0]*r, y+(0.4*i-0.8)*r-0.5, 0.1*r-0.5);
|
||||||
|
g.fillCircle(x+layers[i][1]*r, y+(0.4*i-0.8)*r-0.5, 0.1*r-0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function chooseIcon(condition) {
|
||||||
|
if (!condition) return () => {};
|
||||||
|
condition = condition.toLowerCase();
|
||||||
|
if (condition.includes("thunderstorm")) return drawThunderstorm;
|
||||||
|
if (condition.includes("freezing")||condition.includes("snow")||
|
||||||
|
condition.includes("sleet")) {
|
||||||
|
return drawSnow;
|
||||||
|
}
|
||||||
|
if (condition.includes("drizzle")||
|
||||||
|
condition.includes("shower")) {
|
||||||
|
return drawRain;
|
||||||
|
}
|
||||||
|
if (condition.includes("rain")) return drawShowerRain;
|
||||||
|
if (condition.includes("clear")) return drawSun;
|
||||||
|
if (condition.includes("few clouds")) return drawFewClouds;
|
||||||
|
if (condition.includes("scattered clouds")) return drawCloud;
|
||||||
|
if (condition.includes("clouds")) return drawBrokenClouds;
|
||||||
|
return drawMist;
|
||||||
|
}
|
||||||
|
|
||||||
|
chooseIcon(cond)(x, y, r)
|
||||||
|
},
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
# Weather
|
||||||
|
|
||||||
|
Shows Gadgetbridge weather reports.
|
||||||
|
|
||||||
|
This adds a widget with a weather pictogram and the temperature.
|
||||||
|
You can view the full report through the app:
|
||||||
|

|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
See [this guide](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Weather)
|
||||||
|
to setup Gadgetbridge weather reporting.
|
||||||
|
|
||||||
|
## Controls
|
||||||
|
|
||||||
|
BTN2: opens the launcher
|
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
|
@ -0,0 +1,40 @@
|
||||||
|
(() => {
|
||||||
|
function draw() {
|
||||||
|
const w = require('weather').load()
|
||||||
|
if (!w) return;
|
||||||
|
g.reset();
|
||||||
|
g.setColor(0).fillRect(this.x, this.y, this.x+this.width, this.y+24)
|
||||||
|
if (w.txt) {
|
||||||
|
require('weather').drawIcon(w.txt, this.x+10, this.y+8, 8);
|
||||||
|
}
|
||||||
|
if (w.temp) {
|
||||||
|
let t = require('locale').temp(w.temp); // applies conversion
|
||||||
|
t = t.substr(0, t.length-2); // but we have no room for units
|
||||||
|
g.setFontAlign(0, 1); // center horizontally at bottom of widget
|
||||||
|
g.setFont('6x8', 1);
|
||||||
|
g.setColor(-1)
|
||||||
|
g.drawString(t, this.x+10, this.y+24)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(weather) {
|
||||||
|
require('weather').save(weather);
|
||||||
|
if (!WIDGETS["weather"].width) {
|
||||||
|
WIDGETS["weather"].width = 20
|
||||||
|
Bangle.drawWidgets()
|
||||||
|
} else if (Bangle.isLCDOn()) {
|
||||||
|
WIDGETS["weather"].draw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _GB = global.GB;
|
||||||
|
global.GB = (event) => {
|
||||||
|
if (event.t==="weather") update(event);
|
||||||
|
if (_GB) setTimeout(_GB, 0, event);
|
||||||
|
};
|
||||||
|
|
||||||
|
WIDGETS["weather"] = {area: "tl", width: 20, draw: draw};
|
||||||
|
if (!require('weather').load()) {
|
||||||
|
WIDGETS["weather"].width = 0
|
||||||
|
}
|
||||||
|
})();
|
Loading…
Reference in New Issue