mirror of
https://github.com/vincentbernat/i3wm-configuration.git
synced 2025-07-02 14:24:21 +02:00
polybar: display current weather
This commit is contained in:
parent
eb3e05e488
commit
0559c830ef
5 changed files with 197 additions and 1 deletions
168
bin/polybar-weather
Executable file
168
bin/polybar-weather
Executable file
|
@ -0,0 +1,168 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
"""Get current weather condition and forecast for Polybar."""
|
||||
|
||||
import requests
|
||||
import logging
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
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)
|
||||
logger.info(f'current location is {data["city"]}, {data["country"]}')
|
||||
return (data["lat"], data["lon"])
|
||||
|
||||
|
||||
def get_weather(apikey, latitude, longitude, endpoint):
|
||||
"""Return current weather data from openweathermap."""
|
||||
logger.debug("query openweathermap for %s, %s", latitude, longitude)
|
||||
r = requests.get(
|
||||
f"https://api.openweathermap.org/data/2.5/{endpoint}",
|
||||
params={
|
||||
"appid": apikey,
|
||||
"lat": latitude,
|
||||
"lon": longitude,
|
||||
"units": "metric",
|
||||
"cnt": 3,
|
||||
},
|
||||
)
|
||||
r.raise_for_status()
|
||||
data = r.json()
|
||||
logger.debug("%s data: %s", endpoint, data)
|
||||
return data
|
||||
|
||||
|
||||
def format_weather(data):
|
||||
"""Translate OWM icon to WeatherIcons."""
|
||||
icon = data["weather"][0]["icon"]
|
||||
temperature = data["main"]["temp"]
|
||||
icon = {
|
||||
"01d": "\uf00d", # Clear sky - day
|
||||
"01n": "\uf02e", # Clear sky - night
|
||||
"02d": "\uf002", # Few clouds (11-25%) - day
|
||||
"02n": "\uf086", # Few clouds (11-25%) - night
|
||||
"03d": "\uf041", # Scattered clouds (25-50%) - day/night
|
||||
"03n": "\uf041", # 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": "\uf018", # Shower rain - day
|
||||
"09n": "\uf018", # 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")
|
||||
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",
|
||||
)
|
||||
parser.add_argument("--font", default=4, type=int, help="Weather Icons 1-index")
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
default=f"{os.environ['XDG_RUNTIME_DIR']}/i3/weather.txt",
|
||||
help="Output destination",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--timeout",
|
||||
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:
|
||||
# Wait to be online
|
||||
logger.debug("check if we are online")
|
||||
if subprocess.run(["nm-online", "-s", "-q", "-t", "5"]).returncode != 0:
|
||||
logger.info("not online, waiting")
|
||||
update_status("", options.output)
|
||||
if (
|
||||
subprocess.run(
|
||||
["nm-online", "-s", "-q", "-t", str(options.timeout * 60)]
|
||||
).returncode
|
||||
!= 0
|
||||
):
|
||||
logger.warning("not online, exiting")
|
||||
sys.exit(1)
|
||||
|
||||
# Grab information. We only use forecast weather, otherwise,
|
||||
# we may get something "late".
|
||||
location = get_location()
|
||||
forecast_weather = get_weather(options.owm_api_key, *location, "forecast")
|
||||
description = forecast_weather["list"][0]["weather"][0]["description"]
|
||||
logger.info(f"current weather: {description}")
|
||||
|
||||
# Format output
|
||||
output = "{} {} {}".format(
|
||||
format_weather(forecast_weather["list"][0]),
|
||||
format_weather(forecast_weather["list"][1]),
|
||||
format_weather(forecast_weather["list"][2]),
|
||||
)
|
||||
output = output.replace("%{Tx}", "%%{T%d}" % options.font)
|
||||
logger.debug("output: %s", output)
|
||||
|
||||
update_status(output, options.output)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("%s", e)
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
|
@ -29,9 +29,10 @@ foreground = ${colors.foreground}
|
|||
font-0 = Iosevka:style=Regular:size=10;2
|
||||
font-1 = Font Awesome 6 Pro:style=Solid:size=10;2
|
||||
font-2 = Font Awesome 6 Brands:style=Regular:size=10;2
|
||||
font-3 = Weather Icons:style=Regular:size=10;2
|
||||
|
||||
modules-left = i3
|
||||
modules-center = date
|
||||
modules-center = date weather
|
||||
|
||||
[bar/alone]
|
||||
inherit = bar/common
|
||||
|
@ -43,6 +44,7 @@ modules-right = cpu memory brightness battery bluetooth network disk dunst pulse
|
|||
|
||||
[bar/secondary]
|
||||
inherit = bar/common
|
||||
modules-center = date
|
||||
modules-right = pulseaudio
|
||||
|
||||
[module/i3]
|
||||
|
@ -106,6 +108,12 @@ type = custom/ipc
|
|||
hook-0 = cat $XDG_RUNTIME_DIR/i3/bluetooth.txt 2> /dev/null
|
||||
initial = 1
|
||||
|
||||
[module/weather]
|
||||
type = custom/ipc
|
||||
hook-0 = cat $XDG_RUNTIME_DIR/i3/weather.txt 2> /dev/null
|
||||
initial = 1
|
||||
click-left = xdg-open https://www.meteoblue.com/en/weather/week
|
||||
|
||||
[module/dunst]
|
||||
type = custom/ipc
|
||||
hook-0 = cat $XDG_RUNTIME_DIR/i3/dunst.txt 2> /dev/null
|
||||
|
|
|
@ -3,6 +3,8 @@ Description=i3 session
|
|||
BindsTo=graphical-session.target
|
||||
Wants=wallpaper.service
|
||||
Wants=wallpaper.timer
|
||||
Wants=weather.service
|
||||
Wants=weather.timer
|
||||
Wants=polybar.service
|
||||
Wants=i3-companion.service
|
||||
Wants=misc-x.service
|
||||
|
|
11
dotfiles/systemd/weather.service
Normal file
11
dotfiles/systemd/weather.service
Normal file
|
@ -0,0 +1,11 @@
|
|||
[Unit]
|
||||
Description=Build and display wallpaper
|
||||
PartOf=graphical-session.target
|
||||
After=polybar.service
|
||||
|
||||
[Service]
|
||||
Environment=OWM_API_KEY=81687c78e0376836871a6cb9fc347249
|
||||
ExecStart=/usr/bin/mkdir -p %h/.cache/i3
|
||||
ExecStart=%h/.config/i3/bin/polybar-weather
|
||||
Type=oneshot
|
||||
RemainAfterExit=false
|
7
dotfiles/systemd/weather.timer
Normal file
7
dotfiles/systemd/weather.timer
Normal file
|
@ -0,0 +1,7 @@
|
|||
[Unit]
|
||||
Description=Weather update
|
||||
PartOf=graphical-session.target
|
||||
|
||||
[Timer]
|
||||
OnUnitActiveSec=1h
|
||||
OnClockChange=true
|
Loading…
Add table
Add a link
Reference in a new issue