mirror of https://github.com/espruino/BangleApps
284 lines
8.2 KiB
JavaScript
284 lines
8.2 KiB
JavaScript
|
/*
|
||
|
* Aviator Clock - Bangle.js
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
const COLOUR_DARK_GREY = 0x4208; // same as: g.setColor(0.25, 0.25, 0.25)
|
||
|
const COLOUR_GREY = 0x8410; // same as: g.setColor(0.5, 0.5, 0.5)
|
||
|
const COLOUR_LIGHT_GREY = 0xc618; // same as: g.setColor(0.75, 0.75, 0.75)
|
||
|
const COLOUR_RED = 0xf800; // same as: g.setColor(1, 0, 0)
|
||
|
const COLOUR_BLUE = 0x001f; // same as: g.setColor(0, 0, 1)
|
||
|
const COLOUR_YELLOW = 0xffe0; // same as: g.setColor(1, 1, 0)
|
||
|
const COLOUR_LIGHT_CYAN = 0x87ff; // same as: g.setColor(0.5, 1, 1)
|
||
|
const COLOUR_DARK_YELLOW = 0x8400; // same as: g.setColor(0.5, 0.5, 0)
|
||
|
const COLOUR_DARK_CYAN = 0x0410; // same as: g.setColor(0, 0.5, 0.5)
|
||
|
const COLOUR_ORANGE = 0xfc00; // same as: g.setColor(1, 0.5, 0)
|
||
|
|
||
|
const APP_NAME = 'aviatorclk';
|
||
|
|
||
|
const horizontalCenter = g.getWidth()/2;
|
||
|
const mainTimeHeight = 38;
|
||
|
const secondaryFontHeight = 22;
|
||
|
const dateColour = ( g.theme.dark ? COLOUR_YELLOW : COLOUR_BLUE );
|
||
|
const UTCColour = ( g.theme.dark ? COLOUR_LIGHT_CYAN : COLOUR_DARK_CYAN );
|
||
|
const separatorColour = ( g.theme.dark ? COLOUR_LIGHT_GREY : COLOUR_DARK_GREY );
|
||
|
|
||
|
const avwx = require('avwx');
|
||
|
|
||
|
|
||
|
// read in the settings
|
||
|
var settings = Object.assign({
|
||
|
showSeconds: true,
|
||
|
invertScrolling: false,
|
||
|
}, require('Storage').readJSON(APP_NAME+'.json', true) || {});
|
||
|
|
||
|
|
||
|
// globals
|
||
|
var drawTimeout;
|
||
|
var secondsInterval;
|
||
|
var avwxTimeout;
|
||
|
|
||
|
var AVWXrequest;
|
||
|
var METAR = '';
|
||
|
var METARlinesCount = 0;
|
||
|
var METARscollLines = 0;
|
||
|
var METARts;
|
||
|
|
||
|
|
||
|
|
||
|
// date object to time string in format HH:MM[:SS]
|
||
|
// (with a leading 0 for hours if required, unlike the "locale" time() function)
|
||
|
function timeStr(date, seconds) {
|
||
|
let timeStr = date.getHours().toString();
|
||
|
if (timeStr.length == 1) timeStr = '0' + timeStr;
|
||
|
let minutes = date.getMinutes().toString();
|
||
|
if (minutes.length == 1) minutes = '0' + minutes;
|
||
|
timeStr += ':' + minutes;
|
||
|
if (seconds) {
|
||
|
let seconds = date.getSeconds().toString();
|
||
|
if (seconds.length == 1) seconds = '0' + seconds;
|
||
|
timeStr += ':' + seconds;
|
||
|
}
|
||
|
return timeStr;
|
||
|
}
|
||
|
|
||
|
|
||
|
// draw the METAR info
|
||
|
function drawAVWX() {
|
||
|
let now = new Date();
|
||
|
let METARage = 0; // in minutes
|
||
|
if (METARts) {
|
||
|
METARage = Math.floor((now - METARts) / 60000);
|
||
|
}
|
||
|
|
||
|
g.setBgColor(g.theme.bg);
|
||
|
|
||
|
let y = Bangle.appRect.y + mainTimeHeight + secondaryFontHeight + 4;
|
||
|
g.clearRect(0, y, g.getWidth(), y + (secondaryFontHeight * 4));
|
||
|
|
||
|
g.setFontAlign(0, -1).setFont("Vector", secondaryFontHeight);
|
||
|
if (METARage > 90) { // older than 1.5h
|
||
|
g.setColor(COLOUR_RED);
|
||
|
} else if (METARage > 60) { // older than 1h
|
||
|
g.setColor( g.theme.dark ? COLOUR_ORANGE : COLOUR_DARK_YELLOW );
|
||
|
} else {
|
||
|
g.setColor(g.theme.fg);
|
||
|
}
|
||
|
let METARlines = g.wrapString(METAR, g.getWidth());
|
||
|
METARlinesCount = METARlines.length;
|
||
|
METARlines.splice(0, METARscollLines);
|
||
|
g.drawString(METARlines.join("\n"), horizontalCenter, y, true);
|
||
|
|
||
|
if (! avwxTimeout) { avwxTimeout = setTimeout(updateAVWX, 5 * 60000); }
|
||
|
}
|
||
|
|
||
|
// update the METAR info
|
||
|
function updateAVWX() {
|
||
|
if (avwxTimeout) clearTimeout(avwxTimeout);
|
||
|
avwxTimeout = undefined;
|
||
|
|
||
|
METAR = '\nGetting GPS fix';
|
||
|
METARlinesCount = 0; METARscollLines = 0;
|
||
|
METARts = undefined;
|
||
|
drawAVWX();
|
||
|
|
||
|
Bangle.setGPSPower(true, APP_NAME);
|
||
|
Bangle.on('GPS', fix => {
|
||
|
// prevent multiple, simultaneous requests
|
||
|
if (AVWXrequest) { return; }
|
||
|
|
||
|
if ('fix' in fix && fix.fix != 0 && fix.satellites >= 4) {
|
||
|
Bangle.setGPSPower(false, APP_NAME);
|
||
|
let lat = fix.lat;
|
||
|
let lon = fix.lon;
|
||
|
|
||
|
METAR = '\nRequesting METAR';
|
||
|
METARlinesCount = 0; METARscollLines = 0;
|
||
|
METARts = undefined;
|
||
|
drawAVWX();
|
||
|
|
||
|
// get latest METAR from nearest airport (via AVWX API)
|
||
|
AVWXrequest = avwx.request('metar/'+lat+','+lon, 'onfail=nearest', data => {
|
||
|
if (avwxTimeout) clearTimeout(avwxTimeout);
|
||
|
avwxTimeout = undefined;
|
||
|
|
||
|
let METARjson = JSON.parse(data.resp);
|
||
|
|
||
|
if ('sanitized' in METARjson) {
|
||
|
METAR = METARjson.sanitized;
|
||
|
} else {
|
||
|
METAR = 'No "sanitized" METAR data found!';
|
||
|
}
|
||
|
METARlinesCount = 0; METARscollLines = 0;
|
||
|
|
||
|
if ('time' in METARjson) {
|
||
|
METARts = new Date(METARjson.time.dt);
|
||
|
let now = new Date();
|
||
|
let METARage = Math.floor((now - METARts) / 60000); // in minutes
|
||
|
if (METARage <= 30) {
|
||
|
// some METARs update every 30 min -> attempt to update after METAR is 35min old
|
||
|
avwxTimeout = setTimeout(updateAVWX, (35 - METARage) * 60000);
|
||
|
} else if (METARage <= 60) {
|
||
|
// otherwise, attempt METAR update after it's 65min old
|
||
|
avwxTimeout = setTimeout(updateAVWX, (65 - METARage) * 60000);
|
||
|
}
|
||
|
} else {
|
||
|
METARts = undefined;
|
||
|
}
|
||
|
|
||
|
drawAVWX();
|
||
|
AVWXrequest = undefined;
|
||
|
|
||
|
}, error => {
|
||
|
// AVWX API request failed
|
||
|
console.log(error);
|
||
|
METAR = 'ERR: ' + error;
|
||
|
METARlinesCount = 0; METARscollLines = 0;
|
||
|
METARts = undefined;
|
||
|
drawAVWX();
|
||
|
AVWXrequest = undefined;
|
||
|
});
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
|
||
|
// draw only the seconds part of the main clock
|
||
|
function drawSeconds() {
|
||
|
let now = new Date();
|
||
|
let seconds = now.getSeconds().toString();
|
||
|
if (seconds.length == 1) seconds = '0' + seconds;
|
||
|
let y = Bangle.appRect.y + mainTimeHeight - 3;
|
||
|
g.setFontAlign(-1, 1).setFont("Vector", secondaryFontHeight).setColor(COLOUR_GREY);
|
||
|
g.drawString(seconds, horizontalCenter + 54, y, true);
|
||
|
}
|
||
|
|
||
|
// sync seconds update
|
||
|
function syncSecondsUpdate() {
|
||
|
drawSeconds();
|
||
|
setTimeout(function() {
|
||
|
drawSeconds();
|
||
|
secondsInterval = setInterval(drawSeconds, 1000);
|
||
|
}, 1000 - (Date.now() % 1000));
|
||
|
}
|
||
|
|
||
|
// set timeout for per-minute updates
|
||
|
function queueDraw() {
|
||
|
if (drawTimeout) clearTimeout(drawTimeout);
|
||
|
drawTimeout = setTimeout(function() {
|
||
|
drawTimeout = undefined;
|
||
|
if (METARts) {
|
||
|
let now = new Date();
|
||
|
let METARage = Math.floor((now - METARts) / 60000);
|
||
|
if (METARage > 60) {
|
||
|
// the METAR colour might have to be updated:
|
||
|
drawAVWX();
|
||
|
}
|
||
|
}
|
||
|
draw();
|
||
|
}, 60000 - (Date.now() % 60000));
|
||
|
}
|
||
|
|
||
|
// draw top part of clock (main time, date and UTC)
|
||
|
function draw() {
|
||
|
let now = new Date();
|
||
|
let nowUTC = new Date(now + (now.getTimezoneOffset() * 1000 * 60));
|
||
|
|
||
|
// prepare main clock area
|
||
|
let y = Bangle.appRect.y;
|
||
|
|
||
|
g.setBgColor(g.theme.bg);
|
||
|
|
||
|
// main time display
|
||
|
g.setFontAlign(0, -1).setFont("Vector", mainTimeHeight).setColor(g.theme.fg);
|
||
|
g.drawString(timeStr(now, false), horizontalCenter, y, true);
|
||
|
|
||
|
// prepare second line (UTC and date)
|
||
|
y += mainTimeHeight;
|
||
|
g.clearRect(0, y, g.getWidth(), y + secondaryFontHeight - 1);
|
||
|
|
||
|
// weekday and day of the month
|
||
|
g.setFontAlign(-1, -1).setFont("Vector", secondaryFontHeight).setColor(dateColour);
|
||
|
g.drawString(require("locale").dow(now, 1).toUpperCase() + ' ' + now.getDate(), 0, y, false);
|
||
|
|
||
|
// UTC
|
||
|
g.setFontAlign(1, -1).setFont("Vector", secondaryFontHeight).setColor(UTCColour);
|
||
|
g.drawString(timeStr(nowUTC, false) + "Z", g.getWidth(), y, false);
|
||
|
|
||
|
queueDraw();
|
||
|
}
|
||
|
|
||
|
|
||
|
// initialise
|
||
|
g.clear(true);
|
||
|
|
||
|
// scroll METAR lines on taps
|
||
|
Bangle.setUI("clockupdown", action => {
|
||
|
switch (action) {
|
||
|
case -1: // top tap
|
||
|
if (settings.invertScrolling) {
|
||
|
if (METARscollLines > 0)
|
||
|
METARscollLines--;
|
||
|
} else {
|
||
|
if (METARscollLines < METARlinesCount - 4)
|
||
|
METARscollLines++;
|
||
|
}
|
||
|
break;
|
||
|
case 1: // bottom tap
|
||
|
if (settings.invertScrolling) {
|
||
|
if (METARscollLines < METARlinesCount - 4)
|
||
|
METARscollLines++;
|
||
|
} else {
|
||
|
if (METARscollLines > 0)
|
||
|
METARscollLines--;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
// ignore
|
||
|
}
|
||
|
drawAVWX();
|
||
|
});
|
||
|
|
||
|
// load widgets
|
||
|
Bangle.loadWidgets();
|
||
|
Bangle.drawWidgets();
|
||
|
|
||
|
// draw static separator line
|
||
|
y = Bangle.appRect.y + mainTimeHeight + secondaryFontHeight;
|
||
|
g.setColor(separatorColour);
|
||
|
g.drawLine(0, y, g.getWidth(), y);
|
||
|
|
||
|
// draw times and request METAR
|
||
|
draw();
|
||
|
if (settings.showSeconds)
|
||
|
syncSecondsUpdate();
|
||
|
updateAVWX();
|
||
|
|
||
|
|
||
|
// TMP for debugging:
|
||
|
//METAR = 'YAAA 011100Z 21014KT CAVOK 23/08 Q1018 RMK RF000/0000';
|
||
|
//METAR = 'YAAA 150900Z 14012KT 9999 SCT045 BKN064 26/14 Q1012 RMK RF000/0000 DL-W/DL-NW';
|
||
|
//METAR = 'YAAA 020030Z VRB CAVOK';
|
||
|
//METARts = new Date(Date.now() - 61 * 60000); // 61 to trigger warning, 91 to trigger alert
|
||
|
|