Skip to content

Instantly share code, notes, and snippets.

@mountbatt
Last active December 30, 2023 13:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mountbatt/163c5d8f8bd7978e1f3c06b3dcccf00f to your computer and use it in GitHub Desktop.
Save mountbatt/163c5d8f8bd7978e1f3c06b3dcccf00f to your computer and use it in GitHub Desktop.
Scriptable Widget to get Weather Data from a weewx driven Weather-Station via JSON by weewx-json (https://github.com/teeks99/weewx-json). With Shortcodes this Widget is fully Siri compatible!
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: blue; icon-glyph: sun;
// Check weewx Weather
// Script Updates: https://gist.github.com/mountbatt/163c5d8f8bd7978e1f3c06b3dcccf00f
// Version: 0.7.4
// edit:
let endpoint = "http://www.waldbadviertel-wetter.de/current_minimal.json"
let prefix = "im" // im | in | at ... defines "im Waldbadviertel" oder "in Cologne" (set to "in" or "at" for good english)
let widgetLang = Device.language()
// do not edit:
// language strings
let langWeather
let langHumidity
let langRain
let langWind
let langMessage
let langItsRainingMessage
let langRainIntensityLow
let langRainIntensityHigh
if(widgetLang == "de") {
langWeather = "Wetter"
langHumidity = "RL"
langRain = "R"
langWind = "W"
langMessage = "Aktuell beträgt die Temperatur"
langItsRainingMessage = "Es regnet zur Zeit"
langRainIntensityLow = "ein wenig"
langRainIntensityHigh = "stark"
} else {
langWeather = "Weather"
langHumidity = "RH"
langRain = "R"
langWind = "W"
langMessage = "Currently the temperature"
langItsRainingMessage = "It is currently raining"
langRainIntensityLow = "a bit"
langRainIntensityHigh = "heavily"
}
let req = new Request(endpoint)
let apiResult = await req.loadJSON()
let weatherData = await apiResult
let locationName = weatherData.station.location
// trunkate locationName after comma (Waldbadviertel, Köln => Waldbadviertel)
locationName = locationName.split(',')[0]
// moonEmoji
let moonEmoji = await getMoonPhase("emoji")
let currentRain = weatherData.current['rain rate'].value // with dot 0.2
let sunriseTime = await isDay("sunrise")
let sunsetTime = await isDay("sunset")
let isDayNight = await isDay()
let nextRefresh = new Date(Date.now() + 60 * 2 * 1000); // 5 Min
console.log(nextRefresh)
if (config.runsWithSiri) {
if(weatherData){
let message = langMessage + ' ' + prefix +' '+ locationName +' ' + replaceDot(weatherData.current.temperature.value, 1) + ' ' + weatherData.current.temperature.units + '.';
if(currentRain > 0){
let RainIntensity
if(currentRain > 5 ){
RainIntensity = langRainIntensityHigh
} else {
RainIntensity = langRainIntensityLow
}
message = message + ' ' + langItsRainingMessage + ' ' + RainIntensity
}
Script.setShortcutOutput(message)
}
//Script.complete()
} else if (config.runsInWidget) {
let widget = createWidget(weatherData, moonEmoji, sunriseTime, sunsetTime, isDayNight, currentRain)
widget.refreshAfterDate = nextRefresh;
Script.setWidget(widget)
Script.complete()
} else {
let widget = createWidget(weatherData, moonEmoji, sunriseTime, sunsetTime, isDayNight, currentRain)
widget.refreshAfterDate = nextRefresh;
Script.setWidget(widget)
widget.presentSmall()
Script.complete()
}
function createWidget(weatherData, moonEmoji, sunriseTime, sunsetTime, isDayNight, currentRain) {
let w = new ListWidget()
w.useDefaultPadding()
// add url to open to widget
let widgetLink = weatherData.station.link
if(!widgetLink){
// extract url from endpoint url
var pathArray = endpoint.split( '/' );
var protocol = pathArray[0];
var host = pathArray[2];
var url = protocol + '//' + host;
widgetLink = url
}
if(widgetLink){
w.url = widgetLink
}
// Set gradient background
let daytime
let startColor
let endColor
if(isDayNight == true){
if(currentRain > 0){
// day color during rain
startColor = new Color("#4e6b7d")
endColor = new Color("#5b707d")
} else {
// day color
startColor = new Color("#2e86be")
endColor = new Color("#65a8cb")
}
} else {
// night color
startColor = new Color("#010722")
endColor = new Color("#313b59")
}
let gradient = new LinearGradient()
gradient.colors = [startColor, endColor]
gradient.locations = [0.0, 1]
w.backgroundGradient = gradient
let wHeaderStack = w.addStack()
let wNameStack = wHeaderStack.addStack()
let wEmojiStack = wHeaderStack.addStack()
let wName = wNameStack.addText(langWeather)
wName.font = Font.lightSystemFont(13)
wName.textColor = Color.white()
wEmojiStack.addSpacer()
let wEmoji
if(isDayNight == true){
if(currentRain > 0 && currentRain < 5){
wEmoji = wEmojiStack.addText('🌦️')
}
if(currentRain >= 5){
wEmoji = wEmojiStack.addText('🌧️')
}
if(currentRain == 0){
wEmoji = wEmojiStack.addText('🌤️')
}
} else {
wEmoji = wEmojiStack.addText(moonEmoji)
}
wEmoji.font = Font.regularSystemFont(13)
let wTitle = w.addText(locationName)
wTitle.textColor = Color.white()
wTitle.font = Font.boldSystemFont(13)
w.addSpacer(5)
let wTemp = w.addText(replaceDot(weatherData.current.temperature.value, 1) + '' + weatherData.current.temperature.units)
wTemp.textColor = Color.white()
wTemp.font = Font.lightSystemFont(36)
w.addSpacer(5)
/*
let wHumidity = w.addText(langHumidity + ': ' + replaceDot(weatherData.current.humidity.value) + '' + weatherData.current.humidity.units)
wHumidity.textColor = Color.white()
wHumidity.font = Font.regularSystemFont(12)
let wRain = w.addText(langRain + ': ' + replaceDot(weatherData.current["rain rate"].value, 1) + ' ' + weatherData.current["rain rate"].units)
wRain.textColor = Color.white()
wRain.font = Font.regularSystemFont(12)
*/
let textHumidity = langHumidity + ': ' + replaceDot(weatherData.current.humidity.value) + '' + weatherData.current.humidity.units
let textRain = langRain + ': ' + replaceDot(weatherData.current["rain rate"].value, 2) + ' ' + weatherData.current["rain rate"].units
let wHumidityAndRain = w.addText(textHumidity + " " + textRain)
wHumidityAndRain.textColor = Color.white()
wHumidityAndRain.font = Font.regularSystemFont(11)
w.addSpacer(2)
function getWindDirName(i){
if(i >= 348.75 || i < 11.25){
return "N";
} else if (i >= 11.25 && i < 33.75) {
return "NNO";
} else if (i >= 33.75 && i < 56.25) {
return "NO";
} else if (i >= 56.25 && i < 78.75) {
return "ONO";
} else if (i >= 78.25 && i < 101.25) {
return "O";
} else if (i >= 101.25 && i < 123.75) {
return "OSO";
} else if (i >= 123.75 && i < 146.25) {
return "SO";
} else if (i >= 146.25 && i < 168.75) {
return "SSO";
} else if (i >= 168.75 && i < 191.25) {
return "S";
} else if (i >= 191.25 && i < 213.75) {
return "SSW";
} else if (i >= 213.75 && i < 236.25) {
return "SW";
} else if (i >= 236.25 && i < 258.75) {
return "WSW";
} else if (i >= 258.75 && i < 281.25) {
return "W";
} else if (i >= 281.25 && i < 303.75) {
return "WNW";
} else if (i >= 303.75 && i < 326.25) {
return "NW";
} else if (i >= 326.25 && i < 348.75) {
return "NNW";
} else {
return "";
}
}
let windDirectionText = " "
if(typeof(weatherData.current["wind direction"]) != 'undefined'){
windDirectionText = getWindDirName(weatherData.current["wind direction"].value)
}
let wWind = w.addText(langWind + ': ' + replaceDot(weatherData.current["wind speed"].value, 1) + ' ' + weatherData.current["wind speed"].units + " " + windDirectionText)
wWind.textColor = Color.white()
wWind.font = Font.regularSystemFont(11)
w.addSpacer(2)
let textSunriseTime = new Date(sunriseTime).getHours()+':'+(new Date(sunriseTime).getMinutes()<10?'0':'') + new Date(sunriseTime).getMinutes()
let textSunsetTime = new Date(sunsetTime).getHours()+':'+(new Date(sunsetTime).getMinutes()<10?'0':'') + new Date(sunsetTime).getMinutes()
let wSun = w.addText("☀↑ " + textSunriseTime + " ↓ " + textSunsetTime)
wSun.textColor = Color.white()
wSun.font = Font.regularSystemFont(11)
//w.addSpacer() // push to top
return w
}
function replaceDot(value, decimal = 0){
value = value.toFixed(decimal)
if(widgetLang == "de"){
// if "de" we replace a dot with a comma for decimal numbers
value = value.toString().replace(/\./g, ',')
}
return value
}
async function isDay(response) {
let stationLat = weatherData.station.latitude
let stationLon = weatherData.station.longitude
// get sunrise sunset via https://sunrise-sunset.org/api - thanks!!! <3
let dayEndpoint = "https://api.sunrise-sunset.org/json?lat="+stationLat+"&lng="+stationLon+"&date=today&formatted=0"
let dRequest = new Request(dayEndpoint);
let dApiResult = await dRequest.loadJSON()
if(response && dApiResult.status == "OK"){
if(response == "sunrise") {
return dApiResult.results.sunrise
}
if(response == "sunset") {
return dApiResult.results.sunset
}
} else {
if(dApiResult.status == "OK") {
let sunrise = dApiResult.results.sunrise
sunrise = Math.round(Date.parse(sunrise)/1000)
let sunset = dApiResult.results.sunset
sunset = Math.round(Date.parse(sunset)/1000)
let now = Math.round(Date.now()/1000)
if(now > sunrise && now < sunset){
// day
return true
} else {
// night
return false
}
} else {
const hours = new Date().getHours()
const isDayTime = hours > 6 && hours < 19
return isDayTime // returns true if its daytime
}
}
}
async function getMoonPhase(type = "index"){
let currTime = Math.floor(Date.now() / 1000)
let mEndpoint = "https://api.farmsense.net/v1/moonphases/?d=" + currTime
let mImagePath = "https://www.farmsense.net/demos/images/moonphases/"
let mRequest = new Request(mEndpoint);
let mApiResult = await mRequest.loadJSON()
let moonIndex = mApiResult[0].Index;
let moonPhase = mApiResult[0].Phase;
let moonPhaseSanitized = moonPhase.replace(/\s+/g, '_').toLowerCase();
let moonPhaseEmoji = await getMoonEmoji(mApiResult[0].Age)
mImagePath = mImagePath+moonIndex+'.png'
if(type == "image"){
return mImagePath
}
if(type == "index"){
return moonIndex
}
if(type == "phase"){
return moonPhaseSanitized
}
if(type == "emoji"){
return moonPhaseEmoji
}
}
async function getMoonEmoji(phase){
if( phase < 1.84566 ){ return "🌑" } // new
else if ( phase < 5.53699 ) { return "🌒" } // waxing crescent
else if ( phase < 9.22831 ) { return "🌓" } // first quarter
else if ( phase < 12.91963 ) { return "🌔" } // waxing gibbous
else if ( phase < 16.61096 ) { return "🌕" } // full
else if ( phase < 20.30228 ) { return "🌖" } // waning gibbous
else if ( phase < 23.99361 ) { return "🌗" } // last quarter
else if ( phase < 27.68493 ) { return "🌘" } // waning crescent
else { return "🌑" } // new
}
// end of file
//
@mountbatt
Copy link
Author

mountbatt commented Mar 2, 2021

Bildschirmfoto 2021-03-04 um 10 15 53

Bildschirmfoto 2021-03-03 um 21 53 37

Intro

Mit diesem iOS Widget können aktuelle Wetterdaten von einer weewx Website geladen werden.
Beispiel: http://www.waldbadviertel-wetter.de
Die Daten werden per JSON übermittelt. Dafür muss die Wetterstation die Daten mit dem Plugin weewx-json bereitstellen. Das Plugin findet man hier: https://github.com/teeks99/weewx-json

Anforderungen

  • iOS 14 oder höher
  • Scriptable version 1.5 (oder neuer)

Installation

  • Kopiere den gesamten Sourcecode von oben (klick vorher auf "raw" oben rechts)
  • Öffne die Scriptable App
  • Klick auf das "+" Symbol oben rechts und füge das kopierte Skript ein
  • Klick auf den Titel des Skripts ganz oben und vergebe einen Namen (z.B. Wetter)
  • Speichere das Skript durch Klick auf "Done" oben links
  • Gehe auf deinen iOS Homescreen und drücke irgendwo lang, um in den "Wiggle Mode" zu kommen (mit dem man auch die App Symbole anordnen kann) oder ganz unten am Ende der Widgets auf "Bearbeiten"
  • Drücke das "+" Symbol oben links, blättere dann nach unten zu "Scriptable" (Liste ist alphabetisch), wähle die erste Widget Größe (small / quadratisches Format) und drücke unten auf "Widget hinzufügen"
  • Drücke auf das Widget, um seine Einstellungen zu bearbeiten (optional lang drücken, wenn der Wiggle Modus schon beendet wurde)
    Wähle unter "Script" das oben erstellte aus (Wetter)
  • Warte einen Moment (ca. 10 Sek.), bis das Widget die Daten vom Server geladen hat.

Siri Shortcut einrichten

  • Gehe in die App "Kurzbefehle" und drücke oben rechts auf "+"
  • "Aktion hinzufügen"
  • Im Bereich "Run Script" das neue Script wählen oder das große "+" Icon drücken.
  • Der Befehl muss lauten "Run {dein scriptname} with Parameter". In unserem Fall also "Run Wetter with Parameter.
  • (ggf. auf "Script" drücken um das entsprechende Script auszuwählen)
  • Auf "Mehr anzeigen" drücken und dann in den Einstellungen beide Haken von "Run in App" und "Beim Ausführen anzeigen" deaktivieren
  • Dann das blaue "+" Icon drücken um einen weiteren Befehl hinzuzufügen
  • Oben in die Suchleiste "Text" eingeben und auf "Text sprechen" in den Aktionen drücken
  • Nun muss der zweite Befehl also "Output sprechen" heißen
  • Dann oben auf "Weiter" klicken
  • Nun kann ein Name für den Befehl eingegeben werden. Es kann Probleme geben, wenn Du hier etwas mit dem Wort "Wetter" oder "Temperatur" eingibst (Die Apple-Wetter-App drängelt sich ggf vor). Ich habe es für dieses Beispiel einfach "Waldbadviertel" genannt. Nun kann ich einfach sagen "Hey Siri, Waldbadviertel" und ich bekomme die aktuelle Temperatur vorgelesen. Aktuell geht es bei mir auch mit "Wetter Waldbadviertel" - einfach ausprobieren.
  • Dann "Fertig" drücken

Updates

04.03.2021

  • critical bugfixes for rain-detection and sunrise/sunset time display
  • (rain detection is still under construction)
  • Version: 0.7.1

03.03.2021

  • added better day/night detection based on real data and sunrise sunset info
  • Version: 0.7

03.03.2021

  • added sun/cloud emoji during day, rain emoji during rain, moon only during the night
  • Version: 0.6

03.03.2021

  • added moon phases as emoji
  • Version: 0.5

03.03.2021

  • added day/night mode on background color gradient
  • Version: 0.4

02.03.2021

  • now runs with JSON. Thanks to Frank Dederichs!
  • multilanguage support (de, en)
  • Siri tells you that it rains
  • Version: 0.3

02.03.2021

  • added Siri support
  • Version: 0.2

02.03.2021

  • initial release
  • Version: 0.1

@mralext20
Copy link

mralext20 commented Dec 30, 2023

Hello, i was looking at this code and noticied that the isDay function gets called three times, making a new web request each time. i wrote a patch to fix this.

diff --git "a/.\\theirs.js" "b/.\\mine.js"
index f15e0f1..df1c333 100644
--- "a/.\\theirs.js"
+++ "b/.\\mine.js"
@@ -51,9 +51,8 @@ locationName = locationName.split(',')[0]
 // moonEmoji
 let moonEmoji = await getMoonPhase("emoji")
 let currentRain = weatherData.current['rain rate'].value // with dot 0.2
-let sunriseTime = await isDay("sunrise")
-let sunsetTime = await isDay("sunset")
-let isDayNight = await isDay()
+let [ sunriseTime, sunsetTime, isDayNight ] = await isDay()
+
 let nextRefresh = new Date(Date.now() + 60 * 2 * 1000); // 5 Min
 console.log(nextRefresh)
 
@@ -256,44 +255,30 @@ function replaceDot(value, decimal = 0) {
     return value
 }
 
-async function isDay(response) {
+async function isDay() {
     let stationLat = weatherData.station.latitude
     let stationLon = weatherData.station.longitude
     // get sunrise sunset via https://sunrise-sunset.org/api - thanks!!! <3
     let dayEndpoint = "https://api.sunrise-sunset.org/json?lat=" + stationLat + "&lng=" + stationLon + "&date=today&formatted=0"
     let dRequest = new Request(dayEndpoint);
     let dApiResult = await dRequest.loadJSON()
-    if (response && dApiResult.status == "OK") {
-        if (response == "sunrise") {
-            return dApiResult.results.sunrise
-        }
-        if (response == "sunset") {
-            return dApiResult.results.sunset
-        }
-    } else {
-        if (dApiResult.status == "OK") {
-            let sunrise = dApiResult.results.sunrise
-            sunrise = Math.round(Date.parse(sunrise) / 1000)
-            let sunset = dApiResult.results.sunset
-            sunset = Math.round(Date.parse(sunset) / 1000)
-            let now = Math.round(Date.now() / 1000)
-            if (now > sunrise && now < sunset) {
-                // day
-                return true
-
-            } else {
-                // night
-                return false
+    if (dApiResult.status == "OK") {
+        let sunrise = dApiResult.results.sunrise
+        sunrise = Math.round(Date.parse(sunrise) / 1000)
+        let sunset = dApiResult.results.sunset
+        sunset = Math.round(Date.parse(sunset) / 1000)
+        let now = Math.round(Date.now() / 1000)
+        let isSunUp = now > sunrise && now < sunset
+        return [sunrise, sunset, isSunUp];
 
-            }
-        } else {
-            const hours = new Date().getHours()
-            const isDayTime = hours > 6 && hours < 19
-            return isDayTime // returns true if its daytime
-        }
+    } else {
+        const hours = new Date().getHours()
+        const isDayTime = hours > 6 && hours < 19
+        return [undefined, undefined, isDayTime] // returns true if its daytime
     }
 }
 
+
 async function getMoonPhase(type = "index") {
     let currTime = Math.floor(Date.now() / 1000)
     let mEndpoint = "https://api.farmsense.net/v1/moonphases/?d=" + currTime

i used javascript's destructuring to return multiple variables from a single call. this will reduct the number of API hits to the sunrise sunset API from 3 to 1 per call of the script.

@mountbatt
Copy link
Author

mountbatt commented Dec 30, 2023

@mralext20 Hi Alex, thanks! Good point! Could you be so kind and post the full code here or link to a gist? Thanks! (Or otherwise: how can i mix your changes with mine? I am not that deep into git …)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment