Zambretti Weather Forecaster
Zambretti Forecaster, uses the Barometer for empirical weather forecast in the Northern Hemisphere (see https://web.archive.org/web/20110610213848/http://www.meteormetrics.com/zambretti.htm), similar to weather stations. Watch must be stationary and its height above sea level set. Uses code from widbaroalarm for measurements (boot.js)master
parent
04023e859b
commit
963fc4970e
|
|
@ -0,0 +1 @@
|
||||||
|
require("heatshrink").decompress(atob("mEw4UA/4AB31H//A/hL/ABfABRMD+ALJh5kKn//BRCBCP4ILT/WrBZOq1W/BY/+BYOvBY0DBYZhGh/6BYOrMI0/BYf5qpGGBYWVqtQC4+qqtVqgvF1NVAIIABI4ogBAAdAL4gKEBZg8E/oLiBQpUEgILHJAUFBY4kCioLHQoQKHGAYL4I4RTLNZaDLGA78EAH4AR"))
|
||||||
|
|
@ -0,0 +1,247 @@
|
||||||
|
/**
|
||||||
|
* https://web.archive.org/web/20110610213848/http://www.meteormetrics.com/zambretti.htm
|
||||||
|
*/
|
||||||
|
|
||||||
|
const storage = require('Storage');
|
||||||
|
const Layout = require("Layout");
|
||||||
|
|
||||||
|
let height;
|
||||||
|
let mainScreen = false;
|
||||||
|
|
||||||
|
const ZAMBRETTI_FORECAST = {
|
||||||
|
A: /*LANG*/'Settled Fine',
|
||||||
|
B: /*LANG*/'Fine Weather',
|
||||||
|
C: /*LANG*/'Becoming Fine',
|
||||||
|
D: /*LANG*/'Fine Becoming Less Settled',
|
||||||
|
E: /*LANG*/'Fine, Possibly showers',
|
||||||
|
F: /*LANG*/'Fairly Fine, Improving',
|
||||||
|
G: /*LANG*/'Fairly Fine, Possibly showers, early',
|
||||||
|
H: /*LANG*/'Fairly Fine Showery Later',
|
||||||
|
I: /*LANG*/'Showery Early, Improving',
|
||||||
|
J: /*LANG*/'Changeable Mending',
|
||||||
|
K: /*LANG*/'Fairly Fine, Showers likely',
|
||||||
|
L: /*LANG*/'Rather Unsettled Clearing Later',
|
||||||
|
M: /*LANG*/'Unsettled, Probably Improving',
|
||||||
|
N: /*LANG*/'Showery Bright Intervals',
|
||||||
|
O: /*LANG*/'Showery Becoming more unsettled',
|
||||||
|
P: /*LANG*/'Changeable some rain',
|
||||||
|
Q: /*LANG*/'Unsettled, short fine Intervals',
|
||||||
|
R: /*LANG*/'Unsettled, Rain later',
|
||||||
|
S: /*LANG*/'Unsettled, rain at times',
|
||||||
|
T: /*LANG*/'Very Unsettled, Finer at times',
|
||||||
|
U: /*LANG*/'Rain at times, worse later.',
|
||||||
|
V: /*LANG*/'Rain at times, becoming very unsettled',
|
||||||
|
W: /*LANG*/'Rain at Frequent Intervals',
|
||||||
|
X: /*LANG*/'Very Unsettled, Rain',
|
||||||
|
Y: /*LANG*/'Stormy, possibly improving',
|
||||||
|
Z: /*LANG*/'Stormy, much rain',
|
||||||
|
};
|
||||||
|
|
||||||
|
const ZAMBRETTI_FALLING = {
|
||||||
|
1050: 'A',
|
||||||
|
1040: 'B',
|
||||||
|
1024: 'C',
|
||||||
|
1018: 'H',
|
||||||
|
1010: 'O',
|
||||||
|
1004: 'R',
|
||||||
|
998: 'U',
|
||||||
|
991: 'V',
|
||||||
|
985: 'X',
|
||||||
|
};
|
||||||
|
|
||||||
|
const ZAMBRETTI_STEADY = {
|
||||||
|
1033: 'A',
|
||||||
|
1023: 'B',
|
||||||
|
1014: 'E',
|
||||||
|
1008: 'K',
|
||||||
|
1000: 'N',
|
||||||
|
994: 'P',
|
||||||
|
989: 'S',
|
||||||
|
981: 'W',
|
||||||
|
974: 'X',
|
||||||
|
960: 'Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
const ZAMBRETTI_RISING = {
|
||||||
|
1030: 'A',
|
||||||
|
1022: 'B',
|
||||||
|
1012: 'C',
|
||||||
|
1007: 'F',
|
||||||
|
1000: 'G',
|
||||||
|
995: 'I',
|
||||||
|
990: 'J',
|
||||||
|
984: 'L',
|
||||||
|
978: 'M',
|
||||||
|
970: 'Q',
|
||||||
|
965: 'T',
|
||||||
|
959: 'Y',
|
||||||
|
947: 'Z',
|
||||||
|
};
|
||||||
|
|
||||||
|
function correct_season(letter, dir) {
|
||||||
|
const month = new Date().getMonth() + 1;
|
||||||
|
const location = require("Storage").readJSON("mylocation.json",1)||{"lat":51.5072,"lon":0.1276,"location":"London"};
|
||||||
|
const northern_hemisphere = location.lat > 0;
|
||||||
|
const summer = northern_hemisphere ? (month >= 4 && month <= 9) : (month >= 10 || month <= 3);
|
||||||
|
|
||||||
|
let corr = 0;
|
||||||
|
if (dir < 0 && !summer) { // Winter falling
|
||||||
|
corr = -1;
|
||||||
|
} else if (dir > 0 && summer) { // Summer rising
|
||||||
|
corr = 1;
|
||||||
|
}
|
||||||
|
return String.fromCharCode(letter.charCodeAt(0)+corr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_zambretti_letter(pressure, dir) {
|
||||||
|
let table = () => {
|
||||||
|
if (dir < 0) { // Barometer Falling
|
||||||
|
return ZAMBRETTI_FALLING;
|
||||||
|
} else if (dir == 0) { // Barometer Steady
|
||||||
|
return ZAMBRETTI_STEADY;
|
||||||
|
} else { // Barometer Rising
|
||||||
|
return ZAMBRETTI_RISING;
|
||||||
|
}
|
||||||
|
}();
|
||||||
|
|
||||||
|
const closest = Object.keys(table).reduce(function(prev, curr) {
|
||||||
|
return (Math.abs(curr - pressure) < Math.abs(prev - pressure) ? curr : prev);
|
||||||
|
});
|
||||||
|
|
||||||
|
return correct_season(table[closest], dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadSettings() {
|
||||||
|
const settings = require('Storage').readJSON("zambretti.json", true) || {};
|
||||||
|
height = settings.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showMenu() {
|
||||||
|
const menu = {
|
||||||
|
"" : {
|
||||||
|
title : "Zambretti Forecast",
|
||||||
|
},
|
||||||
|
"< Back": () => {
|
||||||
|
E.showMenu();
|
||||||
|
layout.forgetLazyState();
|
||||||
|
show();
|
||||||
|
},
|
||||||
|
/*LANG*/"Exit": () => load(),
|
||||||
|
/*LANG*/"Settings": () =>
|
||||||
|
eval(require('Storage').read('zambretti.settings.js'))(() => {
|
||||||
|
loadSettings();
|
||||||
|
showMenu();
|
||||||
|
}),
|
||||||
|
'Plot history': () => {E.showMenu(); history();},
|
||||||
|
};
|
||||||
|
E.showMenu(menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
const layout = new Layout({
|
||||||
|
type:"v", c: [
|
||||||
|
{type:"txt", font:"9%", label:/*LANG*/"Zambretti Forecast", bgCol:g.theme.bgH, fillx: true, pad: 1},
|
||||||
|
{type:"txt", font:"12%", id:"forecast", filly: 1, wrap: 1, width: Bangle.appRect.w, pad: 1},
|
||||||
|
{type:"h", c:[
|
||||||
|
{type: 'v', c:[
|
||||||
|
{type:"txt", font:"9%", label:/*LANG*/"Pressure ", pad: 3, halign: -1},
|
||||||
|
{type:"txt", font:"9%", label:/*LANG*/"Difference", pad: 3, halign: -1},
|
||||||
|
{type:"txt", font:"9%", label:/*LANG*/"Temperature", pad: 3, halign: -1},
|
||||||
|
]},
|
||||||
|
{type: 'v', c:[
|
||||||
|
{type:"txt", font:"9%", id:"pressure", pad: 3, halign: -1},
|
||||||
|
{type:"txt", font:"9%", id:"diff", pad: 3, halign: -1},
|
||||||
|
{type:"txt", font:"9%", id:"temp", pad: 3, halign: -1},
|
||||||
|
]}
|
||||||
|
]},
|
||||||
|
]
|
||||||
|
}, {lazy:true});
|
||||||
|
|
||||||
|
function draw(temperature) {
|
||||||
|
const history3 = storage.readJSON("zambretti.log.json", true) || []; // history of recent 3 hours
|
||||||
|
const pressure_cur = history3[history3.length-1].p;
|
||||||
|
const pressure_last = history3[0].p;
|
||||||
|
const diff = pressure_cur - pressure_last;
|
||||||
|
const pressure_sea = Math.round(pressure_cur * Math.pow(1 - (0.0065 * height) / (temperature + (0.0065 * height) + 273.15),-5.257));
|
||||||
|
|
||||||
|
layout.forecast.label = ZAMBRETTI_FORECAST[get_zambretti_letter(pressure_sea, diff)];
|
||||||
|
layout.pressure.label = pressure_sea;
|
||||||
|
layout.diff.label = diff;
|
||||||
|
layout.temp.label = require("locale").number(temperature,1);
|
||||||
|
layout.render();
|
||||||
|
//layout.debug();
|
||||||
|
|
||||||
|
mainScreen = true;
|
||||||
|
Bangle.setUI({
|
||||||
|
mode: "custom",
|
||||||
|
btn: (n) => {mainScreen = false; showMenu();},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function show() {
|
||||||
|
Bangle.getPressure().then(p =>{if (p) draw(p.temperature);});
|
||||||
|
}
|
||||||
|
|
||||||
|
function history() {
|
||||||
|
const interval = 15; // minutes
|
||||||
|
const history3 = require('Storage').readJSON("zambretti.log.json", true) || []; // history of recent 3 hours
|
||||||
|
|
||||||
|
const now = new Date()/(1000);
|
||||||
|
let curtime = now-3*60*60; // 3h ago
|
||||||
|
const data = [];
|
||||||
|
while (curtime <= now) {
|
||||||
|
// find closest value in history for this timestamp
|
||||||
|
const closest = history3.reduce((prev, curr) => {
|
||||||
|
return (Math.abs(curr.ts - curtime) < Math.abs(prev.ts - curtime) ? curr : prev);
|
||||||
|
});
|
||||||
|
data.push(closest.p);
|
||||||
|
curtime += interval*60;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bangle.setUI({
|
||||||
|
mode: "custom",
|
||||||
|
back: () => showMenu(),
|
||||||
|
});
|
||||||
|
|
||||||
|
g.reset().setFont("6x8",1);
|
||||||
|
require("graph").drawLine(g, data, {
|
||||||
|
axes: true,
|
||||||
|
x: 4,
|
||||||
|
y: Bangle.appRect.y+8,
|
||||||
|
height: Bangle.appRect.h-20,
|
||||||
|
gridx: 1,
|
||||||
|
gridy: 1,
|
||||||
|
miny: Math.min.apply(null, data)-1,
|
||||||
|
maxy: Math.max.apply(null, data)+1,
|
||||||
|
title: /*LANG*/"Barometer history (mBar)",
|
||||||
|
ylabel: y => y,
|
||||||
|
xlabel: i => {
|
||||||
|
const t = -3*60 + interval*i;
|
||||||
|
if (t % 60 === 0) {
|
||||||
|
return "-" + t/60 + "h";
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
g.reset().clear();
|
||||||
|
loadSettings();
|
||||||
|
Bangle.loadWidgets();
|
||||||
|
|
||||||
|
if (height === undefined) {
|
||||||
|
// setting of height required
|
||||||
|
eval(require('Storage').read('zambretti.settings.js'))(() => {
|
||||||
|
loadSettings();
|
||||||
|
show();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
Bangle.drawWidgets();
|
||||||
|
|
||||||
|
// Update every 15 minutes
|
||||||
|
setInterval(() => {
|
||||||
|
if (mainScreen) {
|
||||||
|
show();
|
||||||
|
}
|
||||||
|
}, 15*60*1000);
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 716 B |
|
|
@ -0,0 +1,106 @@
|
||||||
|
{
|
||||||
|
// Copied from widbaroalarm
|
||||||
|
const LOG_FILE = "zambretti.log.json";
|
||||||
|
const history3 = require('Storage').readJSON(LOG_FILE, true) || []; // history of recent 3 hours
|
||||||
|
let currentPressures = [];
|
||||||
|
|
||||||
|
isValidPressureValue = (pressure) => {
|
||||||
|
return !(pressure == undefined || pressure <= 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
calculcate3hAveragePressure = () => {
|
||||||
|
if (history3 != undefined && history3.length > 0) {
|
||||||
|
let sum = 0;
|
||||||
|
for (let i = 0; i < history3.length; i++) {
|
||||||
|
sum += history3[i].p;
|
||||||
|
}
|
||||||
|
threeHourAvrPressure = sum / history3.length;
|
||||||
|
} else {
|
||||||
|
threeHourAvrPressure = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
handlePressureValue = (pressure) => {
|
||||||
|
if (pressure == undefined || pressure <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ts = Math.round(Date.now() / 1000); // seconds
|
||||||
|
const d = {"ts" : ts, "p" : pressure};
|
||||||
|
|
||||||
|
history3.push(d);
|
||||||
|
|
||||||
|
// delete oldest entries until we have max 50
|
||||||
|
while (history3.length > 50) {
|
||||||
|
history3.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// delete entries older than 3h
|
||||||
|
for (let i = 0; i < history3.length; i++) {
|
||||||
|
if (history3[i].ts < ts - (3 * 60 * 60)) {
|
||||||
|
history3.shift();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// write data to storage
|
||||||
|
require('Storage').writeJSON(LOG_FILE, history3);
|
||||||
|
|
||||||
|
calculcate3hAveragePressure();
|
||||||
|
};
|
||||||
|
|
||||||
|
barometerPressureHandler = (e) => {
|
||||||
|
const MEDIANLENGTH = 20;
|
||||||
|
while (currentPressures.length > MEDIANLENGTH)
|
||||||
|
currentPressures.pop();
|
||||||
|
|
||||||
|
const pressure = e.pressure;
|
||||||
|
if (isValidPressureValue(pressure)) {
|
||||||
|
currentPressures.unshift(pressure);
|
||||||
|
let median = currentPressures.slice().sort();
|
||||||
|
|
||||||
|
if (median.length > 10) {
|
||||||
|
var mid = median.length >> 1;
|
||||||
|
medianPressure = Math.round(E.sum(median.slice(mid - 4, mid + 5)) / 9);
|
||||||
|
if (medianPressure > 0) {
|
||||||
|
turnOff();
|
||||||
|
handlePressureValue(medianPressure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
turn on barometer power
|
||||||
|
take multiple measurements
|
||||||
|
sort the results
|
||||||
|
take the middle one (median)
|
||||||
|
turn off barometer power
|
||||||
|
*/
|
||||||
|
getPressureValue = () => {
|
||||||
|
Bangle.setBarometerPower(true, "zambretti");
|
||||||
|
Bangle.on('pressure', barometerPressureHandler);
|
||||||
|
setTimeout(turnOff, 30000);
|
||||||
|
};
|
||||||
|
|
||||||
|
turnOff = () => {
|
||||||
|
Bangle.removeListener('pressure', barometerPressureHandler);
|
||||||
|
Bangle.setBarometerPower(false, "zambretti");
|
||||||
|
};
|
||||||
|
|
||||||
|
// delay pressure measurement by interval-lastrun
|
||||||
|
const interval = 15; // minutes
|
||||||
|
const lastRun = history3.length > 0 ? history3[history3.length-1].ts : 0;
|
||||||
|
const lastRunAgo = Math.round(Date.now() / 1000) - lastRun;
|
||||||
|
let diffNextRun = interval*60-lastRunAgo;
|
||||||
|
if (diffNextRun < 0) {
|
||||||
|
diffNextRun = 0; // run asap
|
||||||
|
}
|
||||||
|
setTimeout(() => {
|
||||||
|
if (interval > 0) {
|
||||||
|
setInterval(getPressureValue, interval * 60000);
|
||||||
|
}
|
||||||
|
getPressureValue();
|
||||||
|
}, diffNextRun*1000);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"id": "zambretti",
|
||||||
|
"name": "Zambretti Weather Forecaster",
|
||||||
|
"shortName": "Zb. Weather",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "Zambretti Forecaster, uses the Barometer for empirical weather forecast in the Northern Hemisphere (see https://web.archive.org/web/20110610213848/http://www.meteormetrics.com/zambretti.htm), similar to weather stations. Watch must be stationary and its height above sea level set.",
|
||||||
|
"icon": "app.png",
|
||||||
|
"tags": "outdoors,weather",
|
||||||
|
"supports": ["BANGLEJS2"],
|
||||||
|
"dependencies": {"mylocation":"app"},
|
||||||
|
"screenshots": [ {"url":"screenshot.png"} ],
|
||||||
|
"storage": [
|
||||||
|
{"name":"zambretti.app.js","url":"app.js"},
|
||||||
|
{"name":"zambretti.settings.js","url":"settings.js"},
|
||||||
|
{"name":"zambretti.boot.js","url":"boot.js"},
|
||||||
|
{"name":"zambretti.img","url":"app-icon.js","evaluate":true}
|
||||||
|
],
|
||||||
|
"data": [{"name":"zambretti.json"}, {"name":"zambretti.log.json"}]
|
||||||
|
}
|
||||||
|
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
|
|
@ -0,0 +1,25 @@
|
||||||
|
(function(back) {
|
||||||
|
const FILE = "zambretti.json";
|
||||||
|
// Load settings
|
||||||
|
const settings = Object.assign({
|
||||||
|
height: 0,
|
||||||
|
}, require('Storage').readJSON(FILE, true) || {});
|
||||||
|
|
||||||
|
function writeSettings() {
|
||||||
|
require('Storage').writeJSON(FILE, settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the menu
|
||||||
|
E.showMenu({
|
||||||
|
"" : { "title" : "Zambretti Forecast" },
|
||||||
|
"< Back" : () => back(),
|
||||||
|
'Height above sea level (m)': {
|
||||||
|
value: settings.height,
|
||||||
|
min: 0, max: 1000,
|
||||||
|
onchange: v => {
|
||||||
|
settings.height = v;
|
||||||
|
writeSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
@ -228,5 +228,34 @@
|
||||||
"quarter to *$2": "viertel vor *$2",
|
"quarter to *$2": "viertel vor *$2",
|
||||||
"ten to *$2": "zehn vor *$2",
|
"ten to *$2": "zehn vor *$2",
|
||||||
"five to *$2": "fünf vor *$2"
|
"five to *$2": "fünf vor *$2"
|
||||||
|
},
|
||||||
|
"zambretti": {
|
||||||
|
"//": "App-specific overrides",
|
||||||
|
"Settled Fine": "Beständig sonnig",
|
||||||
|
"Fine Weather": "Sonniges Wetter",
|
||||||
|
"Becoming Fine": "Es wird schöner",
|
||||||
|
"Fine Becoming Less Settled": "Sonnig, Tendenz unbeständiger",
|
||||||
|
"Fine, Possibly showers": "Sonnig, eventuell Schauer",
|
||||||
|
"Fairly Fine, Improving": "Heiter bis wolkig, Besserung zu erwarten",
|
||||||
|
"Fairly Fine, Possibly showers, early": "Heiter bis wolkig, anfangs evtl. Schauer",
|
||||||
|
"Fairly Fine Showery Later": "Heiter bis wolkig, später Regen",
|
||||||
|
"Showery Early, Improving": "Anfangs noch Schauer, dann Besserung",
|
||||||
|
"Changeable Mending": "Wechselhaft mit Schauern",
|
||||||
|
"Fairly Fine, Showers likely": "Heiter bis wolkig, vereinzelt Regen",
|
||||||
|
"Rather Unsettled Clearing Later": "Unbeständig, spaeter Aufklarung",
|
||||||
|
"Unsettled, Probably Improving": "Unbeständig, evtl. Besserung.",
|
||||||
|
"Showery Bright Intervals": "Regnerisch mit heiteren Phasen",
|
||||||
|
"Showery Becoming more unsettled": "Regnerisch, wird unbeständiger",
|
||||||
|
"Changeable some rain": "Wechselhaft mit etwas Regen",
|
||||||
|
"Unsettled, short fine Intervals": "Unbeständig mit heiteren Phasen",
|
||||||
|
"Unsettled, Rain later": "Unbeständig, später Regen",
|
||||||
|
"Unsettled, rain at times": "Unbeständig mit etwas Regen",
|
||||||
|
"Very Unsettled, Finer at times": "Wechselhaft und regnerisch",
|
||||||
|
"Rain at times, worse later.": "Gelegentlich Regen, Verschlechterung",
|
||||||
|
"Rain at times, becoming very unsettled": "Zuweilen Regen, sehr unbeständig",
|
||||||
|
"Rain at Frequent Intervals": "Häufiger Regen",
|
||||||
|
"Very Unsettled, Rain": "Regen, sehr unbeständig",
|
||||||
|
"Stormy, possibly improving": "Stürmisch, evtl. Besserung",
|
||||||
|
"Stormy, much rain": "Stürmisch mit viel Regen"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue