Skip to content

Instantly share code, notes, and snippets.

@wxkeith
Last active September 19, 2022 04:33
Show Gist options
  • Save wxkeith/da736d6a2193705d850e7808fd9b950a to your computer and use it in GitHub Desktop.
Save wxkeith/da736d6a2193705d850e7808fd9b950a to your computer and use it in GitHub Desktop.

WeatherFlow Widget for Scriptable

Requirements

  • You're running iOS/iPadOS 14.
  • You've installed Scriptable (https://scriptable.app).
  • You own a WeatherFlow weather station.

Installation

  1. Create a new script in Scriptable and paste the wxflow-widget.js code into it.
  2. In a web browser, log on to your WeatherFlow account at https://www.tempestwx.com.
  3. Copy your station id from the address bar in your web browser to the STATION_ID variable in wxflow-widget.js (for example, if your address is https://tempestwx.com/station/12345/, your station id is 12345).
  4. Go to Settings, scroll to the bottom and click Data Authorizations, then create a personal use token. Copy your token into the API_KEY variable in wxflow-widget.js.
  5. Long-press anywhere on your home screen to enter edit mode, tap the + in the top left corner, select Scriptable, and add the small variant of the widget.
  6. While in edit mode, tap the new widget, and configure it to use the wxflow-widget.js script.

Recent changes

  • Configuration options to vary the background color or temperature text color according to your outdoor temperature.
  • Parameters now display in the units you've specified in the Tempest app.

Acknowledgements

  • 93ben on the WeatherFlow forum for help with the temperature colors and pointing out the user-specified units returned by the API.
  • GaryFunk on the WeatherFlow forum for posting a consolidated list of formulas and conversions.
  • grantharrison on the WeatherFlow forum for suggesting a cleaner layout.
// Set this to your personal WeatherFlow API key.
const CONFIG_API_KEY = "YOUR API KEY GOES HERE"
// Set this to your station id.
const CONFIG_STATION_ID = 12345
// Set this to true to vary the background color of the widget based on the temperature.
const CONFIG_DYNAMIC_BACKGROUND = false
// Set this to true to vary the color of the temperature text based on the temperature.
const CONFIG_DYNAMIC_TEMPERATURE = true
let jsonData = await requestStationData(CONFIG_API_KEY, CONFIG_STATION_ID)
let widget = await createWidget(jsonData)
if(!config.runsInWidget) {
await widget.presentSmall();
}
Script.setWidget(widget)
Script.complete()
function createWidget(data) {
let w = new ListWidget()
w.spacing = 1
w.addSpacer()
let stationLine = w.addText(data.station_name)
stationLine.font = Font.boldSystemFont(12)
stationLine.textColor = Color.white()
stationLine.centerAlignText()
let temperature = convertTemperature(data.obs[0].air_temperature, data.station_units.units_temp)
if(CONFIG_DYNAMIC_BACKGROUND) {
let bgColor = new LinearGradient()
bgColor.colors = [new Color("#000000"), new Color(temperature["color"])]
bgColor.locations = [0.0, 1.0]
w.backgroundGradient = bgColor
} else {
w.backgroundColor = Color.black()
}
let temperatureLine = w.addText(temperature["display"] + temperature["unit"])
temperatureLine.font = Font.boldSystemFont(28)//regularSystemFont(30)
if(CONFIG_DYNAMIC_TEMPERATURE) {
temperatureLine.textColor = new Color(temperature["color"])
} else {
temperatureLine.textColor = Color.white()
}
temperatureLine.centerAlignText()
let humidityLine = w.addText(data.obs[0].relative_humidity + "%")
humidityLine.font = Font.regularSystemFont(12)
humidityLine.textColor = Color.white()
let precip = convertPrecipitation(data.obs[0].precip_accum_local_day, data.station_units.units_precip)
let precipLine = w.addText(precip["display"] + " " + precip["unit"])
precipLine.font = Font.regularSystemFont(12)
precipLine.textColor = Color.white()
wind = convertWindSpeed(data.obs[0].wind_avg, data.station_units.units_wind)
if(wind == 0) {
displayWind = "Calm"
} else {
windDirection = convertWindDirection(data.obs[0].wind_direction, data.station_units.units_direction)
displayWind = windDirection["display"] + "" + windDirection["unit"] + " " + wind["display"] + " " + wind["unit"]
}
let windLine = w.addText(displayWind)
windLine.font = Font.regularSystemFont(12)
windLine.textColor = Color.white()
let pressure = convertPressure(data.obs[0].sea_level_pressure, data.station_units.units_pressure)
let pressureLine = w.addText(pressure["display"] + " " + pressure["unit"] + " " + pressureTrendEmoji(data.obs[0].pressure_trend))
pressureLine.font = Font.regularSystemFont(12)
pressureLine.textColor = Color.white()
let lightningLine = w.addText("⚡️" + data.obs[0].lightning_strike_count_last_3hr)
lightningLine.font = Font.regularSystemFont(12)
lightningLine.textColor = Color.white()
let date = new Date()
let dateFormatter = new DateFormatter()
dateFormatter.useShortTimeStyle()
let strDate = dateFormatter.string(date)
let timeStamp = w.addText(strDate)
timeStamp.font = Font.lightSystemFont(10)
timeStamp.textColor = Color.white()
timeStamp.rightAlignText()
w.addSpacer()
return w
}
async function requestStationData(key, id) {
let url = "https://swd.weatherflow.com/swd/rest/observations/station/" + id + "?api_key=" + key
let req = new Request(url)
let json = await req.loadJSON()
return json
}
function convertTemperature(temperatureValue, targetTemperatureUnit) {
switch(targetTemperatureUnit) {
case "c": // API default
var temperature = temperatureValue
var displayTemperature = temperatureValue.toFixed(1)
var temperatureUnit = "°C"
var temperatureBackgroundColor = temperatureColor(targetTemperatureUnit, temperature)
break
case "f":
var temperature = (Math.round(((temperatureValue * 9 / 5 + 32) + Number.EPSILON) * 10) / 10)
var displayTemperature = temperature.toFixed(1)
var temperatureUnit = "°F"
var temperatureBackgroundColor = temperatureColor(targetTemperatureUnit, temperature)
break
}
return {"value": temperature, "display": displayTemperature, "unit": temperatureUnit, "color": temperatureBackgroundColor}
}
function convertPressure(pressureValue, targetPressureUnit) {
switch(targetPressureUnit) {
case "mb": // API default
var pressure = pressureValue
var displayPressure = pressure.toFixed(1)
var pressureUnit = "mb"
break
case "inhg":
var pressure = (Math.round(((0.0295301 * pressureValue) + Number.EPSILON) * 100) / 100)
var displayPressure = pressure.toFixed(2)
var pressureUnit = "inHg"
break
case "mmhg":
var pressure = (Math.round(((0.750062 * pressureValue) + Number.EPSILON) * 100) / 100)
var displayPressure = pressure.toFixed(2)
var pressureUnit = "mmHg"
break
case "hpa":
var pressure = pressureValue
var displayPressure = pressure.toFixed(1)
var pressureUnit = "hPa"
break
}
return {"value": pressure, "display": displayPressure, "unit": pressureUnit}
}
function convertPrecipitation(precipValue, targetPrecipUnit) {
switch(targetPrecipUnit) {
case "mm": // API default
var precip = (Math.round(((precipValue) + Number.EPSILON) * 100) / 100)
var displayPrecip = precip.toFixed(2)
var precipUnit = "mm"
break
case "in":
var precip = (Math.round(((precipValue * 0.0393701) + Number.EPSILON) * 100) / 100)
var displayPrecip = precip.toFixed(2)
var precipUnit = "in"
break
case "cm":
var precip = (Math.round(((precipValue * .1) + Number.EPSILON) * 100) / 100)
var displayPrecip = precip.toFixed(2)
var precipUnit = "cm"
break
}
return {"value": precip, "display": displayPrecip, "unit": precipUnit}
}
function convertWindDirection(windDirectionValue, targetWindUnit) {
switch(targetWindUnit) {
case "degrees": // API default
var displayWindDirection = windDirectionValue
var windDirectionUnit = "°"
break
case "cardinal":
let compassDirections = ["N","NNE","NE","ENE","E","ESE","SE","SSE","S","SSW","SW","WSW","W","WNW","NW","NNW","N"]
let index = Math.round((windDirectionValue % 360) / 22.5 )
var displayWindDirection = compassDirections[index]
var windDirectionUnit = ""
break
}
return {"value": windDirectionValue, "display": displayWindDirection, "unit": windDirectionUnit}
}
function convertWindSpeed(windValue, targetWindUnit) {
switch(targetWindUnit) {
case "mps": // API default
var wind = windValue
var displayWind = wind.toFixed(1)
var windUnit = "m/s"
break
case "mph":
var wind = (Math.round(((windValue * 2.2369362920544) + Number.EPSILON) * 10) / 10)
var displayWind = wind.toFixed(1)
var windUnit = "mph"
break
case "kts":
var wind = (Math.round(((windValue * 1.943844) + Number.EPSILON) * 10) / 10)
var displayWind = wind.toFixed(1)
var windUnit = "kts"
break
case "kph":
var wind = (Math.round(((windValue / (5/18)) + Number.EPSILON) * 10) / 10)
var displayWind = wind.toFixed(1)
var windUnit = "km/h"
break
case "bft":
var wind = convertBeaufort(windValue)
var displayWind = wind.toFixed(1)
var windUnit = "bft"
break
case "lfm":
var wind = (Math.round(((windValue * 196.850393700787) + Number.EPSILON) * 10) / 10)
var displayWind = wind.toFixed(1)
var windUnit = "lfm"
break
}
return {"value": wind, "display": displayWind, "unit": windUnit}
}
function convertBeaufort(windValue) {
let beaufort = windValue < .3 ? 0 : windValue < 1.6 ? 1 : windValue < 3.5 ? 2 : windValue < 5.5 ? 3 : windValue < 8 ? 4 : windValue < 10.8 ? 5 : windValue < 13.9 ? 6 : windValue < 17.2 ? 7 : windValue < 20.8 ? 8 : windValue < 24.5 ? 9 : windValue < 28.5 ? 10 : windValue < 32.7 ? 11 : windValue >= 32.7 ? 12 : 0;
return beaufort
}
function temperatureColor(temperatureUnit,temperature)
{
if(temperatureUnit == "c") {
if ( temperature >= 49 ) {
color = "#af3190"
} else if( temperature >= 46 && temperature < 49) {
color = "#bd2f82"
} else if( temperature >= 43 && temperature < 46) {
color = "#cb2e73"
} else if( temperature >= 41 && temperature < 43) {
color = "#d82d6e"
} else if( temperature >= 38 && temperature < 40) {
color = "#e62c51"
} else if( temperature >= 35 && temperature < 38) {
color = "#ff2e1f"
} else if( temperature >= 32 && temperature < 35) {
color = "#ff6e35"
} else if( temperature >= 30 && temperature < 32) {
color = "#f3934e"
} else if( temperature >= 27 && temperature < 30) {
color = "#f3ad63"
} else if( temperature >= 24 && temperature < 27) {
color = "#eaca73"
} else if( temperature >= 21 && temperature < 24) {
color = "#f4e376"
} else if( temperature >= 18 && temperature < 21) {
color = "#fcf574"
} else if( temperature >= 16 && temperature < 18) {
color = "#deec62"
} else if( temperature >= 13 && temperature < 16) {
color = "#b1ea3e"
} else if( temperature >= 10 && temperature < 13) {
color = "#20e93b"
} else if( temperature >= 7 && temperature < 10) {
color = "#18eca5"
} else if( temperature >= 5 && temperature < 7) {
color = "#11e1be"
} else if( temperature >= 2 && temperature < 5) {
color = "#06d8d8"
} else if( temperature >= -1 && temperature < 2) {
color = "#29cff0"
} else if( temperature >= -4 && temperature < -1) {
color = "#4fb2ef"
} else if( temperature >= -7 && temperature < -4) {
color = "#4e99ee"
} else if( temperature >= -9 && temperature < -7) {
color = "#4b6bf9"
} else if( temperature >= -12 && temperature < -9) {
color = "#8e4cf9"
} else if( temperature >= -15 && temperature < -12) {
color = "#a051e1"
} else if( temperature >= -18 && temperature < -15) {
color = "#ae4fd4"
} else if( temperature >= -21 && temperature < -18) {
color = "#bd4dc7"
} else if( temperature >= -23 && temperature < -21) {
color = "#ae61ba"
} else if( temperature >= -26 && temperature < -23) {
color = "#a07aad"
} else if( temperature >= -29 && temperature < -26) {
color = "#9286a0"
} else if( temperature >= -32 && temperature < -29) {
color = "#8f819e"
} else if( temperature >= -34 && temperature < -32) {
color = "#afa4bc"
} else if( temperature >= -37 && temperature < -34) {
color = "#bdb2ca"
} else if( temperature >= -40 && temperature < -37) {
color = "#d2c3d1"
} else if( temperature >= -43 && temperature < -40) {
color = "#e3dbe2"
} else if( temperature >= -46 && temperature < -43) {
color = "#e4e4eb"
} else if( temperature < -46 ) {
color = "#eaeaea"
}
} else if(temperatureUnit == "f") {
if ( temperature >= 120 ) {
color = "#af3190"
} else if( temperature >= 115 && temperature < 120) {
color = "#bd2f82"
} else if( temperature >= 110 && temperature < 115) {
color = "#cb2e73"
} else if( temperature >= 105 && temperature < 110) {
color = "#d82d6e"
} else if( temperature >= 100 && temperature < 105) {
color = "#e62c51"
} else if( temperature >= 95 && temperature < 100) {
color = "#ff2e1f"
} else if( temperature >= 90 && temperature < 95) {
color = "#ff6e35"
} else if( temperature >= 85 && temperature < 90) {
color = "#f3934e"
} else if( temperature >= 80 && temperature < 85) {
color = "#f3ad63"
} else if( temperature >= 75 && temperature < 80) {
color = "#eaca73"
} else if( temperature >= 70 && temperature < 75) {
color = "#f4e376"
} else if( temperature >= 65 && temperature < 70) {
color = "#fcf574"
} else if( temperature >= 60 && temperature < 65) {
color = "#deec62"
} else if( temperature >= 55 && temperature < 60) {
color = "#b1ea3e"
} else if( temperature >= 50 && temperature < 55) {
color = "#20e93b"
} else if( temperature >= 45 && temperature < 50) {
color = "#18eca5"
} else if( temperature >= 40 && temperature < 45) {
color = "#11e1be"
} else if( temperature >= 35 && temperature < 40) {
color = "#06d8d8"
} else if( temperature >= 30 && temperature < 35) {
color = "#29cff0"
} else if( temperature >= 25 && temperature < 30) {
color = "#4fb2ef"
} else if( temperature >= 20 && temperature < 25) {
color = "#4e99ee"
} else if( temperature >= 15 && temperature < 20) {
color = "#4b6bf9"
} else if( temperature >= 10 && temperature < 15) {
color = "#8e4cf9"
} else if( temperature >= 5 && temperature < 10) {
color = "#a051e1"
} else if( temperature >= 0 && temperature < 5) {
color = "#ae4fd4"
} else if( temperature >= -5 && temperature < 0) {
color = "#bd4dc7"
} else if( temperature >= -10 && temperature < -5) {
color = "#ae61ba"
} else if( temperature >= -15 && temperature < -10) {
color = "#a07aad"
} else if( temperature >= -20 && temperature < -15) {
color = "#9286a0"
} else if( temperature >= -25 && temperature < -20) {
color = "#8f819e"
} else if( temperature >= -30 && temperature < -25) {
color = "#afa4bc"
} else if( temperature >= -35 && temperature < -30) {
color = "#bdb2ca"
} else if( temperature >= -40 && temperature < -35) {
color = "#d2c3d1"
} else if( temperature >= -45 && temperature < -40) {
color = "#e3dbe2"
} else if( temperature >= -50 && temperature < -45) {
color = "#e4e4eb"
} else if( temperature < -50 ) {
color = "#eaeaea"
}
}
return color
}
function pressureTrendEmoji(trend) {
var pTrend = { "falling": "↓", "rising": "↑", "steady": "" }
return pTrend[trend]
}
@wxkeith
Copy link
Author

wxkeith commented Dec 18, 2020

It’s on the Tempest website. Starting with step #4 on the installation instructions.

@Patto-star
Copy link

Patto-star commented Dec 18, 2020

Thank WXKeith, i managed to find it but because i had no idea where on website it took some hunting around. Maybe the author should help noobies with API by saying that as i had no idea if i was looking for an API key in scriptable, iphone or tempest.

Cheers

@svedatom
Copy link

svedatom commented Apr 26, 2021

Works great save on possible issue.I don't get the emojis I see on the sample above. I only see the lightening bolt.

I found where they go in the code on each line. Where to the emojis come from?

@rickcnagy
Copy link

This is awesome, thanks for doing it. One suggested change: Weatherflow is sending down the timestamp of the observation, which IMO is technically the more correct time to display in the Widget. You can use it by changing line 87 to:

let date = new Date(data.obs[0].timestamp * 1000);

@Maricha09
Copy link

Maricha09 commented Jan 22, 2022

@wxkeith Just what I was looking for, after a bit of struggling did I get everything inputted right I believed but receive an error for lines 29 - 30!??
This value is coming from The Tempest, the only possibility I could guess caused the error is for some reason the two ö (lowercase o umlaut) in the station name "Fiskarhöjden, Saltsjöbaden" is somehow not accepted.

Any idéa on a way to fix the error?

Screen dump

@wxkeith
Copy link
Author

wxkeith commented Jun 21, 2022

@wxkeith Just what I was looking for, after a bit of struggling did I get everything inputted right I believed but receive an error for lines 29 - 30!?? This value is coming from The Tempest, the only possibility I could guess caused the error is for some reason the two ö (lowercase o umlaut) in the station name "Fiskarhöjden, Saltsjöbaden" is somehow not accepted.

Any idéa on a way to fix the error?

@Maricha09 Apologies for the really late reply, did you ever get this to work? I tested passing around your station name in the code in a few places and it doesn't have a problem. So unless it's coming in from the WeatherFlow API in some unexpected way, there is likely a problem with your API key or station ID. I would start by double checking those values.

You can test them in a web browser by building a URL of the format:

https://swd.weatherflow.com/swd/rest/observations/station/{station_id}?api_key={api_key}

For example:
https://swd.weatherflow.com/swd/rest/observations/station/12345?api_key=11111111-1111-1111-1111-111111111111
(this is not a valid station id or api key and will fail, but replacing them with your values should work!)

@Maricha09
Copy link

Maricha09 commented Jun 21, 2022 via email

@Maricha09
Copy link

Maricha09 commented Jun 22, 2022 via email

@wxkeith
Copy link
Author

wxkeith commented Jun 22, 2022

@Maricha09 What is your numeric station id? This should be visible in the URL immediately after you log into the Tempest portal at https://tempestwx.com. Be sure you're setting this in the CONFIG_STATION_ID variable at the top of the script.

@Maricha09
Copy link

Maricha09 commented Jun 27, 2022 via email

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