Merge pull request #3912 from RKBoss6/Weather-Feels-Like-Updates
[Weather] Add feels-like temperature displaymaster
commit
71a3608ad8
|
|
@ -25,3 +25,4 @@
|
||||||
0.26: Expose update function (for use by iOS integration)
|
0.26: Expose update function (for use by iOS integration)
|
||||||
0.27: Add UV index display
|
0.27: Add UV index display
|
||||||
0.28: Fix UV positioning, hide when 0
|
0.28: Fix UV positioning, hide when 0
|
||||||
|
0.29: Add feels-like temperature data and clock_info
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ You can view the full report through the app:
|
||||||
|
|
||||||
Use the iOS shortcut [here](https://www.icloud.com/shortcuts/73be0ce1076446f3bdc45a5707de5c4d). The shortcut uses Apple Weather for weather updates, and sends a notification, which is read by Bangle.js. To push weather every hour, or interval, you will need to create a shortcut automation for every time you want to push the weather.
|
Use the iOS shortcut [here](https://www.icloud.com/shortcuts/73be0ce1076446f3bdc45a5707de5c4d). The shortcut uses Apple Weather for weather updates, and sends a notification, which is read by Bangle.js. To push weather every hour, or interval, you will need to create a shortcut automation for every time you want to push the weather.
|
||||||
|
|
||||||
|
|
||||||
## Android Setup
|
## Android Setup
|
||||||
|
|
||||||
1. Install [Gadgetbridge for Android](https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/) on your phone.
|
1. Install [Gadgetbridge for Android](https://f-droid.org/packages/nodomain.freeyourgadget.gadgetbridge/) on your phone.
|
||||||
|
|
|
||||||
|
|
@ -9,59 +9,66 @@ Bangle.loadWidgets();
|
||||||
var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [
|
var layout = new Layout({type:"v", bgCol: g.theme.bg, c: [
|
||||||
{filly: 1},
|
{filly: 1},
|
||||||
{type: "h", filly: 0, c: [
|
{type: "h", filly: 0, c: [
|
||||||
{type: "v", width: g.getWidth()/2, c: [ // Vertical container for icon
|
{type: "v", width: g.getWidth()/2, c: [ // Vertical container for icon + UV
|
||||||
{type: "custom", fillx: 1, height: g.getHeight()/2 - 30, valign: -1, txt: "unknown", id: "icon",
|
{type: "custom", fillx: 1, height: (g.getHeight()/2)-10, valign: -1, txt: "unknown", id: "icon",bgCol:g.theme.bg,
|
||||||
render: l => weather.drawIcon(l, l.x+l.w/2, l.y+l.h/2, l.w/2-5)},
|
render: l => weather.drawIcon(l, l.x+l.w/2, l.y+l.h/2, l.w/3)},
|
||||||
]},
|
{type: "custom", fillx: 1, height: 20, id: "uvDisplay",
|
||||||
{type: "v", fillx: 1, c: [
|
|
||||||
{type: "h", pad: 2, c: [
|
|
||||||
{type: "txt", font: "18%", id: "temp", label: "000"},
|
|
||||||
{type: "txt", font: "12%", valign: -1, id: "tempUnit", label: "°C"},
|
|
||||||
]},
|
|
||||||
{filly: 1},
|
|
||||||
{type: "txt", font: "6x8", pad: 2, halign: 1, label: /*LANG*/"Humidity"},
|
|
||||||
{type: "txt", font: "9%", pad: 2, halign: 1, id: "hum", label: "000%"},
|
|
||||||
{type: "txt", font: "6x8", pad: [2, 2, 2, 2], halign: -1, label: /*LANG*/"Wind"},
|
|
||||||
{type: "h", pad: [0, 2, 2, 2], halign: -1, c: [
|
|
||||||
{type: "txt", font: "9%", pad: 2, id: "wind", label: "00"},
|
|
||||||
{type: "txt", font: "6x8", pad: 2, valign: -1, id: "windUnit", label: "km/h"},
|
|
||||||
]},
|
|
||||||
{type: "custom", fillx: 1, height: 15, id: "uvDisplay",
|
|
||||||
render: l => {
|
render: l => {
|
||||||
if (!current || current.uv === undefined || current.uv === 0) return;
|
if (!current || current.uv === undefined) return;
|
||||||
const uv = Math.min(parseInt(current.uv), 11); // Cap at 11
|
const uv = Math.min(parseInt(current.uv), 11); // Cap at 11
|
||||||
|
|
||||||
// UV color thresholds: [max_value, color] based on WHO standards
|
// UV color thresholds: [max_value, color] based on WHO standards
|
||||||
const colors = [[2,"#0F0"], [5,"#FF0"], [7,"#F80"], [10,"#F00"], [11,"#F0F"]];
|
const colors = [[2,"#0F0"], [5,"#FF0"], [7,"#F80"], [10,"#F00"], [11,"#F0F"]];
|
||||||
const color = colors.find(c => uv <= c[0])[1];
|
const color = colors.find(c => uv <= c[0])[1];
|
||||||
const blockH = 8, blockW = 3;
|
|
||||||
|
|
||||||
// Draw UV title and blocks on same line
|
// Setup and measure label
|
||||||
g.setFont("6x8").setFontAlign(-1, 0);
|
g.setFont("6x8").setFontAlign(-1, 0);
|
||||||
const label = "UV";
|
const label = "UV: ";
|
||||||
const labelW = g.stringWidth(label);
|
const labelW = g.stringWidth(label);
|
||||||
|
|
||||||
const x = l.x + 2;
|
// Calculate centered position (4px block + 1px spacing) * blocks - last spacing
|
||||||
const y = l.y + l.h / 2;
|
const totalW = labelW + uv * 5 - (uv > 0 ? 1 : 0);
|
||||||
|
const x = l.x + (l.w - totalW) / 2;
|
||||||
|
const y = l.y + l.h+6;
|
||||||
|
|
||||||
// Draw title
|
// Draw label
|
||||||
g.setColor(g.theme.fg).drawString(label, x, y);
|
g.setColor(g.theme.fg).drawString(label, x, y);
|
||||||
|
|
||||||
// Draw UV blocks after title
|
// Draw UV blocks
|
||||||
g.setColor(color);
|
g.setColor(color);
|
||||||
for (let i = 0; i < uv; i++) {
|
for (let i = 0; i < uv; i++) {
|
||||||
const blockX = x + labelW + 4 + i * (blockW + 2);
|
g.fillRect(x + labelW + i * 5, y - 3, x + labelW + i * 5 + 3, y + 3);
|
||||||
g.fillRect(blockX, y - blockH/2, blockX + blockW, y + blockW/2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset graphics state to prevent interference
|
|
||||||
g.reset();
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
]},
|
]},
|
||||||
|
{type: "v", fillx: 1, c: [
|
||||||
|
{pad:5},
|
||||||
|
{type: "h", pad: 2, c: [
|
||||||
|
{type: "txt", font: "18%", id: "temp", label: "000"},
|
||||||
|
{type: "txt", font: "12%", valign: -1, id: "tempUnit", label: "°C"},
|
||||||
]},
|
]},
|
||||||
{filly: 1},
|
{filly: 1},
|
||||||
{type: "txt", font: "9%", wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: /*LANG*/"Weather condition"},
|
{type: "h", pad: 1, c: [
|
||||||
|
{type: "txt", font: "6x8", pad: 2, halign: 1, label: /*LANG*/"Feels:"},
|
||||||
|
{type: "txt", font: "9%", pad: 2, halign: 1, id: "feelslike", label: "35°F"},
|
||||||
|
]},
|
||||||
|
{filly: 1},
|
||||||
|
{type: "h", pad: 2, c: [
|
||||||
|
{type: "txt", font: "6x8", pad: 2, halign: 1, label: /*LANG*/"Hum:"},
|
||||||
|
{type: "txt", font: "9%", pad: 2, halign: 1, id: "hum", label: "000%"},
|
||||||
|
]},
|
||||||
|
|
||||||
|
{filly: 1},
|
||||||
|
{type: "txt", font: "6x8", pad: 2, halign: -1, label: /*LANG*/"Wind"},
|
||||||
|
{type: "h", halign: -1, c: [
|
||||||
|
{type: "txt", font: "9%", pad: 2, id: "wind", label: "00"},
|
||||||
|
{type: "txt", font: "6x8", pad: 2, valign: -1, id: "windUnit", label: "km/h"},
|
||||||
|
]},
|
||||||
|
]},
|
||||||
|
]},
|
||||||
|
{filly: 1},
|
||||||
|
{type: "txt", font: "9%",wrap: true, height: g.getHeight()*0.18, fillx: 1, id: "cond", label: /*LANG*/"Weather condition"},
|
||||||
{filly: 1},
|
{filly: 1},
|
||||||
{type: "h", c: [
|
{type: "h", c: [
|
||||||
{type: "txt", font: "6x8", pad: 4, id: "loc", label: "Toronto"},
|
{type: "txt", font: "6x8", pad: 4, id: "loc", label: "Toronto"},
|
||||||
|
|
@ -83,8 +90,15 @@ function draw() {
|
||||||
layout.icon.txt = current.txt;
|
layout.icon.txt = current.txt;
|
||||||
layout.icon.code = current.code;
|
layout.icon.code = current.code;
|
||||||
const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/);
|
const temp = locale.temp(current.temp-273.15).match(/^(\D*\d*)(.*)$/);
|
||||||
|
const feelsLikeTemp=locale.temp(current.feels-273.15).match(/^(\D*\d*)(.*)$/);
|
||||||
layout.temp.label = temp[1];
|
layout.temp.label = temp[1];
|
||||||
layout.tempUnit.label = temp[2];
|
layout.tempUnit.label = temp[2];
|
||||||
|
if (!current || current.feels === undefined){
|
||||||
|
layout.feelslike.label = "N/A";
|
||||||
|
}else{
|
||||||
|
layout.feelslike.label = feelsLikeTemp[1]+feelsLikeTemp[2];
|
||||||
|
}
|
||||||
|
|
||||||
layout.hum.label = current.hum+"%";
|
layout.hum.label = current.hum+"%";
|
||||||
const wind = locale.speed(current.wind).match(/^(\D*\d*)(.*)$/);
|
const wind = locale.speed(current.wind).match(/^(\D*\d*)(.*)$/);
|
||||||
layout.wind.label = wind[1];
|
layout.wind.label = wind[1];
|
||||||
|
|
@ -93,7 +107,6 @@ function draw() {
|
||||||
layout.loc.label = current.loc;
|
layout.loc.label = current.loc;
|
||||||
layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; // How to autotranslate this and similar?
|
layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`; // How to autotranslate this and similar?
|
||||||
layout.update();
|
layout.update();
|
||||||
layout.forgetLazyState();
|
|
||||||
layout.render();
|
layout.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,7 +114,6 @@ function drawUpdateTime() {
|
||||||
if (!current || !current.time) return;
|
if (!current || !current.time) return;
|
||||||
layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`;
|
layout.updateTime.label = `${formatDuration(Date.now() - current.time)} ago`;
|
||||||
layout.update();
|
layout.update();
|
||||||
layout.render();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function update() {
|
function update() {
|
||||||
|
|
@ -112,9 +124,9 @@ function update() {
|
||||||
} else {
|
} else {
|
||||||
layout.forgetLazyState();
|
layout.forgetLazyState();
|
||||||
if (NRF.getSecurityStatus().connected) {
|
if (NRF.getSecurityStatus().connected) {
|
||||||
E.showMessage(/*LANG*/"Weather\nunknown\n\nIs Gadgetbridge\nweather\nreporting set\nup on your\nphone?");
|
E.showMessage(/*LANG*/"Weather Data Expired");
|
||||||
} else {
|
} else {
|
||||||
E.showMessage(/*LANG*/"Weather\nunknown\n\nGadgetbridge\nnot connected");
|
E.showMessage(/*LANG*/"Weather\nunknown\n\nPhone\nnot connected");
|
||||||
NRF.on("connect", update);
|
NRF.on("connect", update);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
var weather;
|
var weather;
|
||||||
var weatherLib = require("weather");
|
var weatherLib = require("weather");
|
||||||
|
|
@ -6,6 +7,7 @@
|
||||||
weather = weatherLib.get();
|
weather = weatherLib.get();
|
||||||
if(weather){
|
if(weather){
|
||||||
weather.temp = require("locale").temp(weather.temp-273.15);
|
weather.temp = require("locale").temp(weather.temp-273.15);
|
||||||
|
weather.feels = require("locale").temp(weather.feels-273.15);
|
||||||
weather.hum = weather.hum + "%";
|
weather.hum = weather.hum + "%";
|
||||||
weather.wind = require("locale").speed(weather.wind).match(/^(\D*\d*)(.*)$/);
|
weather.wind = require("locale").speed(weather.wind).match(/^(\D*\d*)(.*)$/);
|
||||||
weather.wind = Math.round(weather.wind[1]) + "kph";
|
weather.wind = Math.round(weather.wind[1]) + "kph";
|
||||||
|
|
@ -14,6 +16,7 @@
|
||||||
temp: "?",
|
temp: "?",
|
||||||
hum: "?",
|
hum: "?",
|
||||||
wind: "?",
|
wind: "?",
|
||||||
|
feels: "?",
|
||||||
txt: "?",
|
txt: "?",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -76,6 +79,18 @@
|
||||||
hide: function () { weatherLib.removeListener("update", this.updater); }
|
hide: function () { weatherLib.removeListener("update", this.updater); }
|
||||||
,run : function() {load("weather.app.js");}
|
,run : function() {load("weather.app.js");}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "feelsLike",
|
||||||
|
hasRange : true,
|
||||||
|
get: () => ({ text: weather.feels, img: atob("GBiBAAAAAAHAAAPgAAfgAAfgAAfg4APhsAfxEB/5EB/5ED/9ED/9ED/9ED/9ED/9EB/9UB/7UA/yyAf26Afk7AfmyAfjGAfh8AAAAA=="),
|
||||||
|
v: parseInt(weather.temp), min: -30, max: 55}),
|
||||||
|
show: function() {
|
||||||
|
this.updater = _updater.bind(this);
|
||||||
|
weatherLib.on("update", this.updater);
|
||||||
|
},
|
||||||
|
hide: function () { weatherLib.removeListener("update", this.updater); }
|
||||||
|
,run : function() {load("weather.app.js");}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "humidity",
|
name: "humidity",
|
||||||
hasRange : true,
|
hasRange : true,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "weather",
|
"id": "weather",
|
||||||
"name": "Weather",
|
"name": "Weather",
|
||||||
"version": "0.28",
|
"version": "0.29",
|
||||||
"description": "Show Gadgetbridge/iOS weather report",
|
"description": "Show Gadgetbridge/iOS weather report",
|
||||||
"icon": "icon.png",
|
"icon": "icon.png",
|
||||||
"screenshots": [{"url":"screenshot.png"}],
|
"screenshots": [{"url":"screenshot.png"}],
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue