2020-05-24 09:21:24 +00:00
|
|
|
const storage = require('Storage');
|
2021-11-20 17:56:45 +00:00
|
|
|
const B2 = process.env.HWVERSION===2;
|
2020-05-24 09:21:24 +00:00
|
|
|
|
2021-09-19 18:28:56 +00:00
|
|
|
let expiryTimeout;
|
2020-05-24 09:21:24 +00:00
|
|
|
function scheduleExpiry(json) {
|
2020-05-24 02:37:38 +00:00
|
|
|
if (expiryTimeout) {
|
|
|
|
clearTimeout(expiryTimeout);
|
|
|
|
expiryTimeout = undefined;
|
|
|
|
}
|
2020-06-07 19:37:31 +00:00
|
|
|
let expiry = "expiry" in json ? json.expiry : 2*3600000;
|
|
|
|
if (json.weather && json.weather.time && expiry) {
|
|
|
|
let t = json.weather.time + expiry - Date.now();
|
2021-09-19 18:28:56 +00:00
|
|
|
expiryTimeout = setTimeout(update, t);
|
2021-06-18 18:16:00 +00:00
|
|
|
}
|
2020-05-24 09:21:24 +00:00
|
|
|
}
|
2020-04-21 21:18:21 +00:00
|
|
|
|
2020-05-24 09:21:24 +00:00
|
|
|
function update(weatherEvent) {
|
|
|
|
let json = storage.readJSON('weather.json')||{};
|
2022-01-04 10:58:04 +00:00
|
|
|
|
2021-09-19 18:28:56 +00:00
|
|
|
if (weatherEvent) {
|
|
|
|
let weather = weatherEvent.clone();
|
|
|
|
delete weather.t;
|
|
|
|
weather.time = Date.now();
|
|
|
|
if (weather.wdir != null) {
|
|
|
|
// Convert numeric direction into human-readable label
|
|
|
|
let deg = weather.wdir;
|
|
|
|
while (deg<0 || deg>360) {
|
|
|
|
deg = (deg+360)%360;
|
|
|
|
}
|
|
|
|
weather.wrose = ['n','ne','e','se','s','sw','w','nw','n'][Math.floor((deg+22.5)/45)];
|
|
|
|
}
|
2020-04-21 21:18:21 +00:00
|
|
|
|
2021-09-19 18:28:56 +00:00
|
|
|
json.weather = weather;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
delete json.weather;
|
|
|
|
}
|
2020-04-21 21:18:21 +00:00
|
|
|
|
2023-11-11 04:44:02 +00:00
|
|
|
|
2021-09-19 18:28:56 +00:00
|
|
|
storage.write('weather.json', json);
|
|
|
|
scheduleExpiry(json);
|
|
|
|
exports.emit("update", json.weather);
|
2020-05-24 09:21:24 +00:00
|
|
|
}
|
2023-11-11 04:44:02 +00:00
|
|
|
exports.update = update;
|
2020-05-24 09:21:24 +00:00
|
|
|
const _GB = global.GB;
|
|
|
|
global.GB = (event) => {
|
|
|
|
if (event.t==="weather") update(event);
|
|
|
|
if (_GB) setTimeout(_GB, 0, event);
|
|
|
|
};
|
|
|
|
|
2021-09-19 18:28:56 +00:00
|
|
|
exports.get = function() {
|
2021-10-04 13:23:12 +00:00
|
|
|
return (storage.readJSON('weather.json')||{}).weather;
|
2021-09-19 18:28:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
scheduleExpiry(storage.readJSON('weather.json')||{});
|
2020-05-24 09:21:24 +00:00
|
|
|
|
2023-01-07 15:00:21 +00:00
|
|
|
function getPalette(monochrome, ovr) {
|
|
|
|
var palette;
|
|
|
|
if(monochrome) {
|
|
|
|
palette = {
|
|
|
|
sun: '#FFF',
|
|
|
|
cloud: '#FFF',
|
|
|
|
bgCloud: '#FFF',
|
|
|
|
rain: '#FFF',
|
|
|
|
lightning: '#FFF',
|
|
|
|
snow: '#FFF',
|
|
|
|
mist: '#FFF',
|
|
|
|
background: '#000'
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
if (B2) {
|
|
|
|
if (ovr.theme.dark) {
|
|
|
|
palette = {
|
|
|
|
sun: '#FF0',
|
|
|
|
cloud: '#FFF',
|
|
|
|
bgCloud: '#777', // dithers on B2, but that's ok
|
|
|
|
rain: '#0FF',
|
|
|
|
lightning: '#FF0',
|
|
|
|
snow: '#FFF',
|
|
|
|
mist: '#FFF'
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
palette = {
|
|
|
|
sun: '#FF0',
|
|
|
|
cloud: '#777', // dithers on B2, but that's ok
|
|
|
|
bgCloud: '#000',
|
|
|
|
rain: '#00F',
|
|
|
|
lightning: '#FF0',
|
|
|
|
snow: '#0FF',
|
|
|
|
mist: '#0FF'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (ovr.theme.dark) {
|
|
|
|
palette = {
|
|
|
|
sun: '#FE0',
|
|
|
|
cloud: '#BBB',
|
|
|
|
bgCloud: '#777',
|
|
|
|
rain: '#0CF',
|
|
|
|
lightning: '#FE0',
|
|
|
|
snow: '#FFF',
|
|
|
|
mist: '#FFF'
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
palette = {
|
|
|
|
sun: '#FC0',
|
|
|
|
cloud: '#000',
|
|
|
|
bgCloud: '#777',
|
|
|
|
rain: '#07F',
|
|
|
|
lightning: '#FC0',
|
|
|
|
snow: '#CCC',
|
|
|
|
mist: '#CCC'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return palette;
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.getColor = function(code) {
|
|
|
|
const codeGroup = Math.round(code / 100);
|
|
|
|
const palette = getPalette(0, g);
|
|
|
|
const cloud = g.blendColor(palette.cloud, palette.bgCloud, .5); //theme independent
|
|
|
|
switch (codeGroup) {
|
|
|
|
case 2: return g.blendColor(cloud, palette.lightning, .5);
|
|
|
|
case 3: return palette.rain;
|
|
|
|
case 5:
|
|
|
|
switch (code) {
|
|
|
|
case 511: return palette.snow;
|
|
|
|
case 520: return g.blendColor(palette.rain, palette.sun, .5);
|
|
|
|
case 521: return g.blendColor(palette.rain, palette.sun, .5);
|
|
|
|
case 522: return g.blendColor(palette.rain, palette.sun, .5);
|
|
|
|
case 531: return g.blendColor(palette.rain, palette.sun, .5);
|
|
|
|
default: return palette.rain;
|
|
|
|
}
|
|
|
|
case 6: return palette.snow;
|
|
|
|
case 7: return palette.mist;
|
|
|
|
case 8:
|
|
|
|
switch (code) {
|
|
|
|
case 800: return palette.sun;
|
|
|
|
case 801: return palette.sun;
|
|
|
|
case 802: return cloud;
|
|
|
|
default: return cloud;
|
|
|
|
}
|
|
|
|
default: return cloud;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-05 11:09:21 +00:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param cond Weather condition, as one of:
|
|
|
|
* {number} code: (Preferred form) https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
|
|
|
|
* {string} weather description (in English: breaks for other languages!)
|
|
|
|
* {object} use cond.code if present, or fall back to cond.txt
|
|
|
|
* @param x Left
|
|
|
|
* @param y Top
|
|
|
|
* @param r Icon Size
|
2022-11-27 14:03:28 +00:00
|
|
|
* @param ovr Graphics instance (or undefined for g)
|
2023-03-04 14:56:50 +00:00
|
|
|
* @param monochrome If true, produce a monochromatic icon
|
2022-01-05 11:09:21 +00:00
|
|
|
*/
|
2023-03-04 14:56:50 +00:00
|
|
|
exports.drawIcon = function(cond, x, y, r, ovr, monochrome) {
|
2021-12-13 03:37:59 +00:00
|
|
|
var palette;
|
2023-03-04 14:56:50 +00:00
|
|
|
if(!ovr) ovr = g;
|
2023-01-07 15:00:21 +00:00
|
|
|
|
|
|
|
palette = getPalette(monochrome, ovr);
|
2022-01-04 10:58:04 +00:00
|
|
|
|
2020-05-24 09:21:24 +00:00
|
|
|
function drawSun(x, y, r) {
|
2022-11-27 14:03:28 +00:00
|
|
|
ovr.setColor(palette.sun);
|
|
|
|
ovr.fillCircle(x, y, r);
|
2020-05-24 09:21:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function drawCloud(x, y, r, c) {
|
|
|
|
const u = r/12;
|
2021-12-13 03:37:59 +00:00
|
|
|
if (c==null) c = palette.cloud;
|
2022-11-27 14:03:28 +00:00
|
|
|
ovr.setColor(c);
|
|
|
|
ovr.fillCircle(x-8*u, y+3*u, 4*u);
|
|
|
|
ovr.fillCircle(x-4*u, y-2*u, 5*u);
|
|
|
|
ovr.fillCircle(x+4*u, y+0*u, 4*u);
|
|
|
|
ovr.fillCircle(x+9*u, y+4*u, 3*u);
|
|
|
|
ovr.fillPoly([
|
2020-05-24 09:21:24 +00:00
|
|
|
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) {
|
2021-12-13 03:37:59 +00:00
|
|
|
drawCloud(x+1/8*r, y-1/8*r, 7/8*r, palette.bgCloud);
|
2022-11-27 14:03:28 +00:00
|
|
|
if(monochrome)
|
|
|
|
drawCloud(x-1/8*r, y+2/16*r, r, palette.background);
|
2020-05-24 09:21:24 +00:00
|
|
|
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);
|
2022-11-27 14:03:28 +00:00
|
|
|
if(monochrome)
|
|
|
|
drawCloud(x-1/8*r, y+2/16*r, r, palette.background);
|
2020-05-24 09:21:24 +00:00
|
|
|
drawCloud(x-1/8*r, y+1/8*r, 7/8*r);
|
|
|
|
}
|
|
|
|
|
|
|
|
function drawRainLines(x, y, r) {
|
2022-11-27 14:03:28 +00:00
|
|
|
ovr.setColor(palette.rain);
|
2020-05-24 09:21:24 +00:00
|
|
|
const y1 = y+1/2*r;
|
|
|
|
const y2 = y+1*r;
|
2022-11-27 14:03:28 +00:00
|
|
|
const poly = ovr.fillPolyAA ? p => ovr.fillPolyAA(p) : p => ovr.fillPoly(p);
|
2021-11-20 17:56:45 +00:00
|
|
|
poly([
|
2021-09-18 17:59:22 +00:00
|
|
|
x-6/12*r, y1,
|
|
|
|
x-8/12*r, y2,
|
2020-05-24 09:21:24 +00:00
|
|
|
x-7/12*r, y2,
|
|
|
|
x-5/12*r, y1,
|
|
|
|
]);
|
2021-11-20 17:56:45 +00:00
|
|
|
poly([
|
2021-09-18 17:59:22 +00:00
|
|
|
x-2/12*r, y1,
|
|
|
|
x-4/12*r, y2,
|
2020-05-24 09:21:24 +00:00
|
|
|
x-3/12*r, y2,
|
|
|
|
x-1/12*r, y1,
|
|
|
|
]);
|
2021-11-20 17:56:45 +00:00
|
|
|
poly([
|
2021-09-18 17:59:22 +00:00
|
|
|
x+2/12*r, y1,
|
|
|
|
x+0/12*r, y2,
|
2020-05-24 09:21:24 +00:00
|
|
|
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) {
|
2022-11-27 14:03:28 +00:00
|
|
|
ovr.setColor(palette.lightning);
|
|
|
|
ovr.fillPoly([
|
2020-05-24 09:21:24 +00:00
|
|
|
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,
|
2020-04-21 21:18:21 +00:00
|
|
|
]);
|
|
|
|
}
|
|
|
|
|
2022-11-27 14:03:28 +00:00
|
|
|
if(monochrome) drawBrokenClouds(x, y-1/3*r, r);
|
2020-05-24 09:21:24 +00:00
|
|
|
drawLightning(x-1/12*r, y+1/2*r, 1/2*r);
|
2022-11-27 14:03:28 +00:00
|
|
|
drawBrokenClouds(x, y-1/3*r, r);
|
2020-05-24 09:21:24 +00:00
|
|
|
}
|
2020-04-21 21:18:21 +00:00
|
|
|
|
2020-05-24 09:21:24 +00:00
|
|
|
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;
|
2020-04-21 21:18:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-27 14:03:28 +00:00
|
|
|
ovr.setColor(palette.snow);
|
2020-05-24 09:21:24 +00:00
|
|
|
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);
|
2022-11-27 14:03:28 +00:00
|
|
|
ovr.fillPoly(points);
|
2020-04-21 21:18:21 +00:00
|
|
|
|
2020-05-24 09:21:24 +00:00
|
|
|
for(let j = -1; j<=1; j += 2) {
|
2020-04-21 21:18:21 +00:00
|
|
|
const points = [
|
2020-05-24 09:21:24 +00:00
|
|
|
x+w, y+7/12*r,
|
|
|
|
x-w, y+7/12*r,
|
2020-04-21 21:18:21 +00:00
|
|
|
x-w, y+r,
|
|
|
|
x+w, y+r,
|
|
|
|
];
|
2020-05-24 09:21:24 +00:00
|
|
|
rotatePoints(points, x, y+7/12*r, j/3*Math.PI);
|
2020-04-21 21:18:21 +00:00
|
|
|
rotatePoints(points, x, y, i/3*Math.PI);
|
2022-11-27 14:03:28 +00:00
|
|
|
ovr.fillPoly(points);
|
2020-04-21 21:18:21 +00:00
|
|
|
}
|
|
|
|
}
|
2020-05-24 09:21:24 +00:00
|
|
|
}
|
2020-04-21 21:18:21 +00:00
|
|
|
|
2020-05-24 09:21:24 +00:00
|
|
|
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],
|
|
|
|
];
|
|
|
|
|
2022-11-27 14:03:28 +00:00
|
|
|
ovr.setColor(palette.mist);
|
2020-05-24 09:21:24 +00:00
|
|
|
for(let i = 0; i<5; ++i) {
|
2022-11-27 14:03:28 +00:00
|
|
|
ovr.fillRect(x+layers[i][0]*r, y+(0.4*i-0.9)*r, x+layers[i][1]*r,
|
2020-05-24 09:21:24 +00:00
|
|
|
y+(0.4*i-0.7)*r-1);
|
2022-11-27 14:03:28 +00:00
|
|
|
ovr.fillCircle(x+layers[i][0]*r, y+(0.4*i-0.8)*r-0.5, 0.1*r-0.5);
|
|
|
|
ovr.fillCircle(x+layers[i][1]*r, y+(0.4*i-0.8)*r-0.5, 0.1*r-0.5);
|
2020-04-21 21:18:21 +00:00
|
|
|
}
|
2020-05-24 09:21:24 +00:00
|
|
|
}
|
2020-04-21 21:18:21 +00:00
|
|
|
|
2021-09-18 17:59:22 +00:00
|
|
|
function drawUnknown(x, y, r) {
|
2021-12-13 03:37:59 +00:00
|
|
|
drawCloud(x, y, r, palette.bgCloud);
|
2022-11-27 14:03:28 +00:00
|
|
|
ovr.setColor(ovr.theme.fg).setFontAlign(0, 0).setFont('Vector', r*2).drawString("?", x+r/10, y+r/6);
|
2021-09-18 17:59:22 +00:00
|
|
|
}
|
|
|
|
|
2022-01-05 11:09:21 +00:00
|
|
|
/*
|
|
|
|
* Choose weather icon to display based on weather description
|
|
|
|
*/
|
|
|
|
function chooseIconByTxt(txt) {
|
|
|
|
if (!txt) return () => {};
|
|
|
|
txt = txt.toLowerCase();
|
|
|
|
if (txt.includes("thunderstorm")) return drawThunderstorm;
|
|
|
|
if (txt.includes("freezing")||txt.includes("snow")||
|
|
|
|
txt.includes("sleet")) {
|
2020-05-24 09:21:24 +00:00
|
|
|
return drawSnow;
|
2020-04-21 21:18:21 +00:00
|
|
|
}
|
2022-01-05 11:09:21 +00:00
|
|
|
if (txt.includes("drizzle")||
|
|
|
|
txt.includes("shower")) {
|
2020-05-24 09:21:24 +00:00
|
|
|
return drawRain;
|
|
|
|
}
|
2022-01-05 11:09:21 +00:00
|
|
|
if (txt.includes("rain")) return drawShowerRain;
|
|
|
|
if (txt.includes("clear")) return drawSun;
|
|
|
|
if (txt.includes("few clouds")) return drawFewClouds;
|
|
|
|
if (txt.includes("scattered clouds")) return drawCloud;
|
|
|
|
if (txt.includes("clouds")) return drawBrokenClouds;
|
|
|
|
if (txt.includes("mist") ||
|
|
|
|
txt.includes("smoke") ||
|
|
|
|
txt.includes("haze") ||
|
|
|
|
txt.includes("sand") ||
|
|
|
|
txt.includes("dust") ||
|
|
|
|
txt.includes("fog") ||
|
|
|
|
txt.includes("ash") ||
|
|
|
|
txt.includes("squalls") ||
|
|
|
|
txt.includes("tornado")) {
|
2021-09-19 18:34:06 +00:00
|
|
|
return drawMist;
|
|
|
|
}
|
2021-09-18 17:59:22 +00:00
|
|
|
return drawUnknown;
|
2020-05-24 09:21:24 +00:00
|
|
|
}
|
2020-04-21 21:18:21 +00:00
|
|
|
|
2022-01-04 10:58:04 +00:00
|
|
|
/*
|
|
|
|
* Choose weather icon to display based on weather conditition code
|
|
|
|
* https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
|
|
|
|
*/
|
|
|
|
function chooseIconByCode(code) {
|
|
|
|
const codeGroup = Math.round(code / 100);
|
|
|
|
switch (codeGroup) {
|
|
|
|
case 2: return drawThunderstorm;
|
|
|
|
case 3: return drawRain;
|
|
|
|
case 5:
|
|
|
|
switch (code) {
|
2022-01-04 11:10:15 +00:00
|
|
|
case 511: return drawSnow;
|
2022-01-04 10:58:04 +00:00
|
|
|
case 520: return drawShowerRain;
|
|
|
|
case 521: return drawShowerRain;
|
|
|
|
case 522: return drawShowerRain;
|
|
|
|
case 531: return drawShowerRain;
|
|
|
|
default: return drawRain;
|
|
|
|
}
|
|
|
|
case 6: return drawSnow;
|
|
|
|
case 7: return drawMist;
|
|
|
|
case 8:
|
|
|
|
switch (code) {
|
|
|
|
case 800: return drawSun;
|
|
|
|
case 801: return drawFewClouds;
|
|
|
|
case 802: return drawCloud;
|
|
|
|
default: return drawBrokenClouds;
|
|
|
|
}
|
|
|
|
default: return drawUnknown;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-05 11:09:21 +00:00
|
|
|
function chooseIcon(cond) {
|
|
|
|
if (typeof (cond)==="object") {
|
|
|
|
if ("code" in cond) return chooseIconByCode(cond.code);
|
|
|
|
if ("txt" in cond) return chooseIconByTxt(cond.txt);
|
|
|
|
} else if (typeof (cond)==="number") {
|
|
|
|
return chooseIconByCode(cond.code);
|
|
|
|
} else if (typeof (cond)==="string") {
|
|
|
|
return chooseIconByTxt(cond.txt);
|
|
|
|
}
|
|
|
|
return drawUnknown;
|
2022-01-04 10:58:04 +00:00
|
|
|
}
|
2022-01-05 11:09:21 +00:00
|
|
|
chooseIcon(cond)(x, y, r);
|
2022-01-04 10:58:04 +00:00
|
|
|
|
2020-05-24 09:21:24 +00:00
|
|
|
};
|