2021-08-10 13:08:15 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
"""Get current weather condition and forecast for Polybar."""
|
|
|
|
|
|
|
|
import requests
|
|
|
|
import logging
|
|
|
|
import argparse
|
|
|
|
import sys
|
|
|
|
import os
|
|
|
|
import subprocess
|
2021-08-11 07:55:12 +02:00
|
|
|
import time
|
2021-08-10 13:08:15 +02:00
|
|
|
from systemd import journal
|
|
|
|
|
|
|
|
logger = logging.getLogger("polybar-weather")
|
|
|
|
|
|
|
|
|
|
|
|
def get_location():
|
|
|
|
"""Return current location as latitude/longitude tuple."""
|
|
|
|
logger.debug("query ip-api.com for location")
|
|
|
|
r = requests.get("http://ip-api.com/json")
|
|
|
|
r.raise_for_status()
|
|
|
|
data = r.json()
|
|
|
|
logger.debug("current location data: %s", data)
|
2021-08-10 21:45:09 +02:00
|
|
|
logger.info(f'current location: {data["city"]}, {data["country"]}')
|
2021-08-10 13:08:15 +02:00
|
|
|
return (data["lat"], data["lon"])
|
|
|
|
|
|
|
|
|
2021-08-11 08:28:15 +02:00
|
|
|
def get_weather(apikey, latitude, longitude, endpoint, count=1):
|
2021-08-10 17:13:31 +02:00
|
|
|
"""Return forecasts data from openweathermap."""
|
2021-08-10 13:08:15 +02:00
|
|
|
logger.debug("query openweathermap for %s, %s", latitude, longitude)
|
|
|
|
r = requests.get(
|
2021-08-11 08:28:15 +02:00
|
|
|
f"https://api.openweathermap.org/data/2.5/{endpoint}",
|
2021-08-10 13:08:15 +02:00
|
|
|
params={
|
|
|
|
"appid": apikey,
|
|
|
|
"lat": latitude,
|
|
|
|
"lon": longitude,
|
|
|
|
"units": "metric",
|
2021-08-11 08:28:15 +02:00
|
|
|
"cnt": count,
|
2021-08-10 13:08:15 +02:00
|
|
|
},
|
|
|
|
)
|
|
|
|
r.raise_for_status()
|
|
|
|
data = r.json()
|
2021-08-11 08:28:15 +02:00
|
|
|
logger.debug("%s data: %s", endpoint, data)
|
2021-08-10 13:08:15 +02:00
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
def format_weather(data):
|
|
|
|
"""Translate OWM icon to WeatherIcons."""
|
2021-08-10 13:35:16 +02:00
|
|
|
# https://erikflowers.github.io/weather-icons/
|
2021-08-10 13:08:15 +02:00
|
|
|
icon = data["weather"][0]["icon"]
|
|
|
|
temperature = data["main"]["temp"]
|
2021-08-10 13:33:21 +02:00
|
|
|
if icon == "01d" and temperature > 32:
|
|
|
|
icon = "\uf072"
|
|
|
|
else:
|
|
|
|
icon = {
|
|
|
|
"01d": "\uf00d", # Clear sky - day
|
|
|
|
"01n": "\uf02e", # Clear sky - night
|
|
|
|
"02d": "\uf002", # Few clouds (11-25%) - day
|
|
|
|
"02n": "\uf083", # Few clouds (11-25%) - night
|
|
|
|
"03d": "\uf041", # Scattered clouds (25-50%) - day/night
|
|
|
|
"03n": "\uf086", # Scattered clouds (25-50%) - day/night
|
|
|
|
"04d": "\uf013", # Broken / Overcast clouds (51-84% / 85-100%) - day/night
|
|
|
|
"04n": "\uf013", # Broken / Overcast clouds (51-84% / 85-100%) - day/night
|
|
|
|
"09d": "\uf009", # Shower rain - day
|
|
|
|
"09n": "\uf037", # Shower rain - night
|
|
|
|
"10d": "\uf008", # Moderate / heavy rain - day
|
|
|
|
"10n": "\uf036", # Moderate / heavy rain - night
|
|
|
|
"11d": "\uf005", # Thunderstorm - day
|
|
|
|
"11n": "\uf025", # Thunderstorm - night
|
|
|
|
"13d": "\uf00a", # Snow - day
|
|
|
|
"13n": "\uf02a", # Snow - night
|
|
|
|
"50d": "\uf003", # Fog - day
|
|
|
|
"50n": "\uf04a", # Fog - night
|
|
|
|
}.get(icon, "\uf075")
|
2021-08-10 13:08:15 +02:00
|
|
|
return "".join(
|
|
|
|
[
|
|
|
|
"%{Tx}",
|
|
|
|
icon,
|
|
|
|
"%{T-} ",
|
|
|
|
str(int(round(temperature))),
|
|
|
|
"°C",
|
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def update_status(status, output):
|
|
|
|
"""Update current status."""
|
|
|
|
# Write it to file
|
|
|
|
with open(output, "w") as f:
|
|
|
|
f.write(status)
|
|
|
|
|
|
|
|
# Send it to polybar
|
|
|
|
subprocess.run(["polybar-msg", "action", f"#weather.send.{status}"], check=True)
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
# Parse
|
|
|
|
description = sys.modules[__name__].__doc__
|
|
|
|
parser = argparse.ArgumentParser(description=description)
|
|
|
|
parser.add_argument(
|
|
|
|
"--debug",
|
|
|
|
"-d",
|
|
|
|
action="store_true",
|
|
|
|
default=False,
|
|
|
|
help="enable debugging",
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"--owm-api-key",
|
|
|
|
default=os.environ.get("OWM_API_KEY"),
|
|
|
|
help="OpenWeatherMap API key",
|
|
|
|
)
|
2021-08-10 17:13:31 +02:00
|
|
|
parser.add_argument(
|
2021-08-11 08:28:15 +02:00
|
|
|
"--forecasts", default=2, type=int, help="Number of forecasts to fetch"
|
2021-08-10 17:13:31 +02:00
|
|
|
)
|
|
|
|
parser.add_argument(
|
|
|
|
"--font-index", default=4, type=int, help="Weather Icons 1-index"
|
|
|
|
)
|
2021-08-10 13:08:15 +02:00
|
|
|
parser.add_argument(
|
|
|
|
"--output",
|
|
|
|
default=f"{os.environ['XDG_RUNTIME_DIR']}/i3/weather.txt",
|
|
|
|
help="Output destination",
|
|
|
|
)
|
|
|
|
parser.add_argument(
|
2021-08-10 17:13:31 +02:00
|
|
|
"--online-timeout",
|
2021-08-10 13:08:15 +02:00
|
|
|
default=30,
|
|
|
|
type=int,
|
|
|
|
help="Wait up to TIMEOUT minutes to be online",
|
|
|
|
)
|
|
|
|
options = parser.parse_args()
|
|
|
|
|
|
|
|
# Logging
|
|
|
|
root = logging.getLogger("")
|
|
|
|
root.setLevel(logging.WARNING)
|
|
|
|
logger.setLevel(options.debug and logging.DEBUG or logging.INFO)
|
|
|
|
if sys.stderr.isatty():
|
|
|
|
ch = logging.StreamHandler()
|
|
|
|
ch.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
|
|
|
|
root.addHandler(ch)
|
|
|
|
else:
|
|
|
|
root.addHandler(journal.JournalHandler(SYSLOG_IDENTIFIER=logger.name))
|
|
|
|
|
|
|
|
try:
|
2021-08-10 21:45:09 +02:00
|
|
|
# Get location
|
|
|
|
try:
|
|
|
|
location = get_location()
|
|
|
|
except requests.exceptions.ConnectionError:
|
|
|
|
# Wait to be online
|
|
|
|
time.sleep(2)
|
2021-08-10 13:08:15 +02:00
|
|
|
logger.info("not online, waiting")
|
|
|
|
update_status("", options.output)
|
2021-08-10 21:45:09 +02:00
|
|
|
process = subprocess.run(
|
|
|
|
["nm-online", "-s", "-q", "-t", str(options.online_timeout * 60)]
|
|
|
|
)
|
|
|
|
if process.returncode != 0:
|
2021-08-10 13:08:15 +02:00
|
|
|
logger.warning("not online, exiting")
|
|
|
|
sys.exit(1)
|
2021-08-10 21:45:09 +02:00
|
|
|
location = get_location()
|
2021-08-10 13:08:15 +02:00
|
|
|
|
2021-08-11 08:28:15 +02:00
|
|
|
# Grab current weather and forecast
|
|
|
|
current_weather = get_weather(options.owm_api_key, *location, "weather")
|
2021-08-10 17:13:31 +02:00
|
|
|
forecast_weather = get_weather(
|
2021-08-11 08:28:15 +02:00
|
|
|
options.owm_api_key, *location, "forecast", count=options.forecasts
|
2021-08-10 17:13:31 +02:00
|
|
|
)
|
2021-08-11 08:28:15 +02:00
|
|
|
description = current_weather["weather"][0]["description"]
|
|
|
|
city = current_weather["name"]
|
2021-08-10 17:20:18 +02:00
|
|
|
logger.info(f"current weather at {city}: {description}")
|
2021-08-10 13:08:15 +02:00
|
|
|
|
|
|
|
# Format output
|
2021-08-11 08:28:15 +02:00
|
|
|
conditions = [format_weather(data)
|
|
|
|
for data in [current_weather] + forecast_weather["list"]]
|
|
|
|
while len(conditions) >= 2:
|
|
|
|
if conditions[-1] == conditions[-2]:
|
|
|
|
conditions.pop()
|
2021-08-10 13:14:22 +02:00
|
|
|
else:
|
|
|
|
break
|
2021-08-11 08:28:15 +02:00
|
|
|
output = " ".join(conditions).replace("%{Tx}", "%%{T%d}" % options.font_index)
|
2021-08-10 13:08:15 +02:00
|
|
|
logger.debug("output: %s", output)
|
|
|
|
|
|
|
|
update_status(output, options.output)
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
logger.exception("%s", e)
|
|
|
|
sys.exit(1)
|
|
|
|
sys.exit(0)
|