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]
}
@owen123t
Copy link

it seems i have to have a tempest device to have the access for the station

Do you have a WeatherFlow device? I've only tested it against what their API returns for a Tempest.

I don't have any Weatherflow devices. it means i could not use this script ?

@wxkeith
Copy link
Author

wxkeith commented Sep 27, 2020

@owen123t Unfortunately, the script requires API access to a WeatherFlow account, so it won't work if you don't own a device.

@kokos1405
Copy link

I'm getting the following error:

Error on line 34:39:
ReferenceError: Can't find variable:convertTemperature

@wxkeith
Copy link
Author

wxkeith commented Oct 22, 2020

I'm getting the following error:

Error on line 34:39:
ReferenceError: Can't find variable:convertTemperature

@kokos1405, Try starting out with a fresh copy of the script and double check your API key and station id. It's probably best copying the source using the "Raw" option at the top of the script. I just did that using this source and it's working here.

@kokos1405
Copy link

I'm getting the following error:
Error on line 34:39:
ReferenceError: Can't find variable:convertTemperature

@kokos1405, Try starting out with a fresh copy of the script and double check your API key and station id. It's probably best copying the source using the "Raw" option at the top of the script. I just did that using this source and it's working here.

Thanks, it's working now. Is it possible to open the Tempest app when pressing the widget?

@nak0604
Copy link

nak0604 commented Nov 13, 2020

I just tried using this, pasted the raw format and filled in the information. I tried adding it and I get this error:

2020-11-13 01:40:42: Error on line 79:104: ReferenceError: Can't find variable: pressureTrendEmoji

@emanonx
Copy link

emanonx commented Nov 23, 2020

I just tried using this, pasted the raw format and filled in the information. I tried adding it and I get this error:

2020-11-13 01:40:42: Error on line 79:104: ReferenceError: Can't find variable: pressureTrendEmoji

You probably missed line 407-410 when you copy/paste. Check code.

@Patto-star
Copy link

Hey everyone,

I am having one problem i cant seem to find where API key is generated for the script is it in my Iphone, Scriptable or Tempest ?

Thanks for any replies.

@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