From 319307cdd1831b27a94b6a9150cf24e24379ae22 Mon Sep 17 00:00:00 2001 From: Richard de Boer Date: Tue, 21 Apr 2020 23:18:21 +0200 Subject: [PATCH] weather: Show Gadgetbridge weather reports Based on http://forum.espruino.com/comments/15194626/, where NebbishHacker did most of the actual work :-) --- apps.json | 17 ++++ apps/weather/app.js | 54 +++++++++++ apps/weather/icon.js | 1 + apps/weather/icon.png | Bin 0 -> 1173 bytes apps/weather/lib.js | 176 ++++++++++++++++++++++++++++++++++++ apps/weather/readme.md | 16 ++++ apps/weather/screenshot.png | Bin 0 -> 4039 bytes apps/weather/widget.js | 40 ++++++++ 8 files changed, 304 insertions(+) create mode 100644 apps/weather/app.js create mode 100644 apps/weather/icon.js create mode 100644 apps/weather/icon.png create mode 100644 apps/weather/lib.js create mode 100644 apps/weather/readme.md create mode 100644 apps/weather/screenshot.png create mode 100644 apps/weather/widget.js diff --git a/apps.json b/apps.json index 95a845f54..3921e8ae1 100644 --- a/apps.json +++ b/apps.json @@ -348,6 +348,23 @@ {"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", "name": "Battery Level Widget", "icon": "widget.png", diff --git a/apps/weather/app.js b/apps/weather/app.js new file mode 100644 index 000000000..fdf28f0e0 --- /dev/null +++ b/apps/weather/app.js @@ -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'}) +})() diff --git a/apps/weather/icon.js b/apps/weather/icon.js new file mode 100644 index 000000000..18ca2b0c9 --- /dev/null +++ b/apps/weather/icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwhC/AE8N6AXV7vdFyoXBGCQUBAAoXp73u93tC6YWBAAIXSFwQwDRiAWDGASSBmYABIx5IDgYXCmC7KCoYRBnvUCwQwKC4gSEAAgwKC5gwKCwPjC6inBC6owBC6wVKPBXd6YXMDBAuNJJQXWfwZITC/6QIBw073ezR6m73anOJAwuHeBAYFIw4WJAAsL3YQOAAxeBCiWIC4e72AJChGACpMIxAXBIwIwEBIIXKBgouDEIYuLC4ghEC6ELLoYXPU4YXFLxQNBBgcLEQqqQFwYA/AH4AYA==")) diff --git a/apps/weather/icon.png b/apps/weather/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bbfc0ace0b8d25c4afefcb336ea04df799a70f22 GIT binary patch literal 1173 zcmV;G1Zw+fgKelF z+6!B~5F)8y)e1%MqEWnPiWg$RgkaGND+=ED(@M!jy(#RCC2ci@HZ&m;H8wSEeq1-3 zna_*eO*h%I$(hL}OJN@v*!|9#@4WBKnQzV+C{Un)Z={DV>E%eib@(DFUXn-NOejAm zNe^8p4lfCJt4`j{fhJF^Xh3?ARSnST?;@FEt!47{!1jEGWkFpPY~BdvcO+sr|AoPp7;j(Z^c9nYrv8 zkRG}g{?t`1FFr+i3Dj4?(4UDMy&OqZr_)1HX<73JfL%aC@{G&CN!PWHJ+npq?bSfm z3XxGNw?6LPs7g_(j(pdPtX6NkmEpEmb0c6>eVOf?QhKnNuV{Lt@F=40(L-~ zBr4U6#P%`av@$0Zg!qTMN57+X49^FVjkbLQP)KEm&RPQbv6&-%R5y%N$)W|^G z3HAX4eumxfNt%QZOugspBgMdEG}xzWFfy>SLB*_~-Ht~Sp@@Yn(5USLDV>K=tk6T&Gr@s zpI_!oxJY!Hg>_~@60*CA@DI5jXU)oJfI#||^z>SYhgIBx&7a<=4-nV|vaT9J`>ZY?00000NkvXXu0mjfi9tF% literal 0 HcmV?d00001 diff --git a/apps/weather/lib.js b/apps/weather/lib.js new file mode 100644 index 000000000..f87984fe5 --- /dev/null +++ b/apps/weather/lib.js @@ -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 {}; + 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) + }, +} diff --git a/apps/weather/readme.md b/apps/weather/readme.md new file mode 100644 index 000000000..a326b67dd --- /dev/null +++ b/apps/weather/readme.md @@ -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: +![Screenshot](screenshot.png) + +## Setup + +See [this guide](https://codeberg.org/Freeyourgadget/Gadgetbridge/wiki/Weather) +to setup Gadgetbridge weather reporting. + +## Controls + +BTN2: opens the launcher diff --git a/apps/weather/screenshot.png b/apps/weather/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..ce79b3b646bfebdad6c29f57779c018ce0ce457e GIT binary patch literal 4039 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 Fe*nCRR)+up literal 0 HcmV?d00001 diff --git a/apps/weather/widget.js b/apps/weather/widget.js new file mode 100644 index 000000000..7488b7657 --- /dev/null +++ b/apps/weather/widget.js @@ -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 + } +})();