Initial commit
This commit is contained in:
commit
0d87588870
224 changed files with 554 additions and 0 deletions
42
modules/__init__.py
Normal file
42
modules/__init__.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
import os
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
class Config:
|
||||
|
||||
def __init__(self):
|
||||
if os.path.exists('/usr/share/fonts/source-pro/'):
|
||||
self.fnt_R_path = "/usr/share/fonts/source-pro/SourceSansPro-Regular.otf"
|
||||
self.fnt_RI_path = "/usr/share/fonts/source-pro/SourceSansPro-It.otf"
|
||||
self.fnt_RB_path = "/usr/share/fonts/source-pro/SourceSansPro-Bold.otf"
|
||||
elif os.path.exists('/usr/share/fonts/libertine'):
|
||||
self.fnt_R_path = "/usr/share/fonts/libertine/LinBiolinum_Rah.ttf"
|
||||
self.fnt_RI_path = "/usr/share/fonts/libertine/LinBiolinum_RIah.ttf"
|
||||
self.fnt_RB_path = "/usr/share/fonts/libertine/LinBiolinum_RBah.ttf"
|
||||
else:
|
||||
self.fnt_R_path = "/usr/share/fonts/TTF/LinBiolinum_Rah.ttf"
|
||||
self.fnt_RI_path = "/usr/share/fonts/TTF/LinBiolinum_RIah.ttf"
|
||||
self.fnt_RB_path = "/usr/share/fonts/TTF/LinBiolinum_RBah.ttf"
|
||||
|
||||
from pygal.style import Style
|
||||
self.pygal_custom_style = Style(
|
||||
background='white',
|
||||
foreground='black',
|
||||
font_family='Source Code Pro',
|
||||
gauge_background="red",
|
||||
colors=('#000', '#777'))
|
||||
|
||||
self.charts_opts = {'style': self.pygal_custom_style, 'show_legend': False, 'margin_right': 7, 'margin_left': 2, 'margin_bottom': 1, 'margin_top': 1}
|
||||
|
||||
class RuleModule:
|
||||
|
||||
def __init__(self):
|
||||
self.coeff = 0.92
|
||||
|
||||
def draw_module(self, config, width, height):
|
||||
image = Image.new('RGB', (width, height), 'white')
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
draw.rectangle((int((1-self.coeff) * width), 0, int(self.coeff * width), height), fill="black")
|
||||
|
||||
return image
|
||||
259
modules/weather.py
Normal file
259
modules/weather.py
Normal file
|
|
@ -0,0 +1,259 @@
|
|||
from datetime import datetime, timezone, timedelta
|
||||
import io
|
||||
import math
|
||||
|
||||
from PIL import Image, ImageDraw, ImageFont
|
||||
|
||||
import pygal
|
||||
|
||||
from .weather_api import WeatherAPI
|
||||
|
||||
def draw_format_infos(infos, width, height, fnt_R, fnt_B, label_margin, align_height=0, margin_bf_first=True, **kwargs):
|
||||
image = Image.new('RGBA', (int(width), int(height)))
|
||||
draw = ImageDraw.Draw(image)
|
||||
|
||||
nb_infos = len(infos.keys())
|
||||
size = 0
|
||||
for k,v in infos.items():
|
||||
size += fnt_R.getsize(k)[0]
|
||||
size += label_margin
|
||||
size += fnt_B.getsize(v)[0]
|
||||
|
||||
margin = (width - size) / nb_infos
|
||||
align = 0
|
||||
if margin_bf_first:
|
||||
align += margin / 2
|
||||
for k,v in infos.items():
|
||||
draw.text(
|
||||
(align, align_height),
|
||||
k,
|
||||
font=fnt_R, **kwargs
|
||||
)
|
||||
align += fnt_R.getsize(k)[0] + label_margin
|
||||
draw.text(
|
||||
(align, align_height),
|
||||
v,
|
||||
font=fnt_B, **kwargs
|
||||
)
|
||||
align += fnt_B.getsize(v)[0] + margin
|
||||
|
||||
return image
|
||||
|
||||
class WeatherToolbarModule:
|
||||
|
||||
def __init__(self):
|
||||
self.label_margin = 3
|
||||
|
||||
def draw_module(self, config, width, height):
|
||||
image = Image.new('RGB', (width, height), 'black')
|
||||
|
||||
draw = ImageDraw.Draw(image)
|
||||
fnt_R = ImageFont.truetype(config.fnt_R_path, 16)
|
||||
fnt_B = ImageFont.truetype(config.fnt_RB_path, 20)
|
||||
|
||||
weather = WeatherAPI().get_currently()
|
||||
|
||||
infos = {
|
||||
"Vent": "%d km/h" % weather["windSpeed"],
|
||||
"Humidité": "%d%%" % (weather["humidity"] * 100),
|
||||
"Indice UV": str(weather["uvIndex"]),
|
||||
"Pression": "%d hPa" % weather["pressure"],
|
||||
}
|
||||
|
||||
txt = draw_format_infos(infos, width, height, fnt_R, fnt_B, self.label_margin, align_height=height/2, anchor="lm")
|
||||
image.paste(txt, (0,0), txt)
|
||||
|
||||
return image
|
||||
|
||||
class WeatherJumboCurrentModule:
|
||||
|
||||
def __init__(self):
|
||||
self.middle_align = 1/3
|
||||
|
||||
def draw_module(self, config, width, height):
|
||||
image = Image.new('RGB', (width, height), 'white')
|
||||
|
||||
draw = ImageDraw.Draw(image)
|
||||
fnt_Big = ImageFont.truetype(config.fnt_RB_path, 26)
|
||||
fnt_R = ImageFont.truetype(config.fnt_R_path, 16)
|
||||
fnt_B = ImageFont.truetype(config.fnt_RB_path, 16)
|
||||
|
||||
# current
|
||||
curweather = WeatherAPI().get_currently()
|
||||
|
||||
icon = Image.open("icons/" + WeatherAPI.get_icon(curweather["icon"])).resize((height, height))
|
||||
image.paste(icon, (int(width*self.middle_align - height), 0), icon)
|
||||
|
||||
draw.text(
|
||||
(width*self.middle_align, height/3),
|
||||
"%d˚ %s." % (math.trunc(curweather["temperature"]), curweather["summary"]),
|
||||
fill="black", anchor="lb", font=fnt_Big
|
||||
)
|
||||
|
||||
thisdayweather = WeatherAPI().get_daily()["data"][0]
|
||||
|
||||
infos = {
|
||||
"Ressentie": "%d˚" % curweather["apparentTemperature"],
|
||||
"Minimale": "%d˚" % thisdayweather["temperatureLow"],
|
||||
"Maximale": "%d˚" % thisdayweather["temperatureHigh"],
|
||||
}
|
||||
|
||||
txt = draw_format_infos(infos, (1-self.middle_align) * width, 20, fnt_R, fnt_B, 5, margin_bf_first=False, fill="black", anchor="lt")
|
||||
image.paste(txt, (int(self.middle_align*width),int(height/2.6)), txt)
|
||||
|
||||
# day
|
||||
fnt_Rig = ImageFont.truetype(config.fnt_R_path, 20)
|
||||
|
||||
dayweather = WeatherAPI().get_hourly()
|
||||
|
||||
draw.text(
|
||||
(width*self.middle_align, height/1.6),
|
||||
dayweather["summary"],
|
||||
fill="black", anchor="lt", font=fnt_Rig
|
||||
)
|
||||
|
||||
return image
|
||||
|
||||
class WeatherSunMoonModule:
|
||||
|
||||
def draw_module(self, config, width, height):
|
||||
image = Image.new('RGB', (width, height), 'white')
|
||||
|
||||
draw = ImageDraw.Draw(image)
|
||||
fnt_R = ImageFont.truetype(config.fnt_R_path, 15)
|
||||
|
||||
icon = Image.open("icons/" + WeatherAPI().get_moon_icon()).resize((height, height))
|
||||
image.paste(icon, (0,0), icon)
|
||||
|
||||
thisdayweather = WeatherAPI().get_daily()["data"][0]
|
||||
|
||||
import time
|
||||
infos = {
|
||||
"sunrise": datetime.fromtimestamp(thisdayweather["sunriseTime"], tz=timezone.utc).strftime("%X"),
|
||||
"sunset": datetime.fromtimestamp(thisdayweather["sunsetTime"], tz=timezone.utc).strftime("%X"),
|
||||
"4": "",
|
||||
}
|
||||
|
||||
i = 0
|
||||
line_size = int(height/len(infos.keys()))
|
||||
for icon, info in infos.items():
|
||||
if info == "":
|
||||
continue
|
||||
icon = Image.open("icons/wi-" + icon + ".png").resize((line_size, line_size))
|
||||
image.paste(icon, (height,i * line_size), icon)
|
||||
draw.text(
|
||||
(height + line_size, i * line_size + line_size / 2),
|
||||
info,
|
||||
fill="black", anchor="lm", font=fnt_R
|
||||
)
|
||||
i += 1
|
||||
|
||||
return image
|
||||
|
||||
class WeatherRainModule:
|
||||
|
||||
def draw_module(self, config, width, height):
|
||||
thisdayweather = WeatherAPI().get_daily()["data"][0]
|
||||
|
||||
gauge = pygal.SolidGauge(half_pie=True, inner_radius=0.70, width=width, height=height*1.55, style=config.pygal_custom_style, show_legend=False, margin_top=-height*0.6, margin_left=1, margin_right=1)
|
||||
|
||||
percent_formatter = lambda x: '{:.10g}%'.format(x)
|
||||
gauge.value_formatter = percent_formatter
|
||||
|
||||
gauge.add('Rain', [{'value': thisdayweather["precipProbability"] * 100 + 1}])
|
||||
|
||||
image = Image.open(io.BytesIO(gauge.render_to_png()))
|
||||
|
||||
icon = Image.open("icons/wi-umbrella.png").resize((int(height/1.25), int(height/1.25)))
|
||||
image.paste(icon, (int(width/2-height/2.5), int(height-height/1.25)), icon)
|
||||
|
||||
return image
|
||||
|
||||
class WeatherTemperatureModule:
|
||||
|
||||
def __init__(self):
|
||||
self.limit_futur = 30
|
||||
|
||||
def draw_module(self, config, width, height):
|
||||
thisdayweather = WeatherAPI().get_daily()["data"][0]
|
||||
|
||||
line_chart = pygal.Line(interpolate='cubic', width=width, height=height, inverse_y_axis=False, x_label_rotation=45, secondary_range=(0,100) if thisdayweather["precipProbability"] > 0 else (0,8), **config.charts_opts)
|
||||
line_chart.value_formatter = lambda x: "%d" % x
|
||||
|
||||
hours_weather = WeatherAPI().get_hourly()
|
||||
|
||||
line_chart.x_labels = [datetime.fromtimestamp(d["time"], tz=timezone.utc).strftime("%Hh") for d in hours_weather["data"][:self.limit_futur]]
|
||||
|
||||
line_chart.add('Températures', [d["temperature"] for d in hours_weather["data"][:self.limit_futur]], show_dots=False)
|
||||
|
||||
if thisdayweather["precipProbability"] > 0:
|
||||
line_chart.add('Précipitations', [d["precipProbability"] * 100 for d in hours_weather["data"][:self.limit_futur]], secondary=True, show_dots=False)
|
||||
else:
|
||||
line_chart.add('Précipitations', [d["uvIndex"] for d in hours_weather["data"][:self.limit_futur]], secondary=True, show_dots=False)
|
||||
|
||||
return Image.open(io.BytesIO(line_chart.render_to_png()))
|
||||
|
||||
class WeeklyWeatherModule:
|
||||
|
||||
def __init__(self):
|
||||
self.first_day = 1
|
||||
self.limit_futur = 7
|
||||
|
||||
def draw_module(self, config, width, height):
|
||||
image = Image.new('RGB', (width, height), 'white')
|
||||
draw = ImageDraw.Draw(image)
|
||||
fnt_R = ImageFont.truetype(config.fnt_R_path, 12)
|
||||
|
||||
weekweather = WeatherAPI().get_daily()
|
||||
daysweather = weekweather["data"]
|
||||
|
||||
nbdays = len(daysweather[self.first_day:self.first_day+self.limit_futur])
|
||||
day_size = int(height / (nbdays + 1))
|
||||
|
||||
draw.text(
|
||||
(day_size + 3, day_size / 2),
|
||||
"%s" % (weekweather["summary"]),
|
||||
fill="black", anchor="lm", font=fnt_R
|
||||
)
|
||||
|
||||
temp = []
|
||||
for day in daysweather[self.first_day:self.first_day+self.limit_futur]:
|
||||
temp.append(day["temperatureLow"])
|
||||
temp.append(day["temperatureHigh"])
|
||||
t_min = min(temp)
|
||||
t_max = max(temp)
|
||||
t_scale = (width - day_size - 30) / (t_max - t_min)
|
||||
|
||||
i = 1
|
||||
for day in daysweather[self.first_day:self.first_day+self.limit_futur]:
|
||||
icon = Image.open("icons/" + WeatherAPI.get_icon(day["icon"])).resize((day_size, day_size))
|
||||
image.paste(icon, (0, i * day_size), icon)
|
||||
|
||||
draw.text(
|
||||
(15 + 2 + day_size + int((day["temperatureLow"]-t_min)*t_scale), i*day_size + 4),
|
||||
"%d˚" % math.trunc(day["temperatureLow"]),
|
||||
fill="black", anchor="rt", font=fnt_R
|
||||
)
|
||||
draw.text(
|
||||
(day_size + (width - day_size) / 2, (i + 1) * day_size - 4),
|
||||
day["summary"],
|
||||
fill="black", anchor="mb", font=fnt_R
|
||||
)
|
||||
draw.text(
|
||||
(day_size + int((day["temperatureHigh"]-t_min)*t_scale) + 2, i * day_size + 4),
|
||||
"%d˚" % math.trunc(day["temperatureHigh"]),
|
||||
fill="black", anchor="lt", font=fnt_R
|
||||
)
|
||||
|
||||
try:
|
||||
draw.rounded_rectangle(
|
||||
(15 + day_size + int((day["temperatureLow"]-t_min)*t_scale),i*day_size + 4,day_size + int((day["temperatureHigh"]-t_min)*t_scale),i*day_size + 14),
|
||||
radius=5, fill="black")
|
||||
except AttributeError:
|
||||
draw.rectangle(
|
||||
(15 + day_size + int((day["temperatureLow"]-t_min)*t_scale),i*day_size + 4,day_size + int((day["temperatureHigh"]-t_min)*t_scale),i*day_size + 14),
|
||||
fill="black")
|
||||
i += 1
|
||||
|
||||
|
||||
return image
|
||||
153
modules/weather_api.py
Normal file
153
modules/weather_api.py
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
from datetime import datetime, timedelta, timezone
|
||||
import json
|
||||
import os
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
|
||||
class DarkSkyAPI:
|
||||
|
||||
def __init__(self, apikey=None, gps="48.8127,2.3437", opts={"lang": "fr", "units": "ca"}):
|
||||
self.apikey = apikey or os.environ["DARKSKYAPIKEY"]
|
||||
self.baseurl = "https://api.darksky.net/forecast"
|
||||
self.default_gps = gps
|
||||
self.opts = opts
|
||||
|
||||
self._cached_file = ".darksky-%s.cache"
|
||||
|
||||
|
||||
def get_weather(self, gps=None):
|
||||
if gps is None:
|
||||
gps = self.default_gps
|
||||
|
||||
# Read the mod time
|
||||
statinfo = None
|
||||
try:
|
||||
statinfo = os.stat(self._cached_file % gps)
|
||||
except:
|
||||
pass
|
||||
|
||||
if statinfo is None or datetime.fromtimestamp(statinfo.st_mtime, tz=timezone.utc) > datetime.now(tz=timezone.utc) + timedelta(hours=1):
|
||||
# Do the request and save it
|
||||
with urllib.request.urlopen(self.baseurl + "/" + str(self.apikey) + "/" + gps + "?" + "&".join([opt+"="+self.opts[opt] for opt in self.opts])) as f:
|
||||
with open(self._cached_file % gps, 'wb') as fd:
|
||||
fd.write(f.read())
|
||||
|
||||
# Retrieve cached data
|
||||
res = {}
|
||||
with open(self._cached_file % gps) as f:
|
||||
res = json.load(f)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def get_icon(icon):
|
||||
if icon == "clear-day":
|
||||
return "wi-day-sunny.png"
|
||||
elif icon == "clear-night":
|
||||
return "wi-night-clear.png"
|
||||
elif icon == "rain":
|
||||
return "wi-day-rain.png"
|
||||
elif icon == "snow":
|
||||
return "wi-day-snow.png"
|
||||
elif icon == "sleet":
|
||||
return "wi-day-sleet.png"
|
||||
elif icon == "wind":
|
||||
return "wi-day-windy.png"
|
||||
elif icon == "fog":
|
||||
return "wi-day-fog.png"
|
||||
elif icon == "cloudy":
|
||||
return "wi-cloudy.png"
|
||||
elif icon == "partly-cloudy-day":
|
||||
return "wi-day-cloudy.png"
|
||||
elif icon == "partly-cloudy-night":
|
||||
return "wi-night-partly-cloudy.png"
|
||||
elif icon == "hail":
|
||||
return "wi-day-hail.png"
|
||||
elif icon == "thunderstorm":
|
||||
return "wi-thunderstorm.png"
|
||||
elif icon == "tornado":
|
||||
return "wi-tornado.png"
|
||||
else:
|
||||
return "wi-alien.png"
|
||||
|
||||
|
||||
def get_moon_icon(self, day=0):
|
||||
moon_phase = self.get_daily()["data"][day]["moonPhase"]
|
||||
|
||||
if moon_phase <= 0:
|
||||
return "wi-moon-alt-new.png"
|
||||
elif moon_phase <= 0.035:
|
||||
return "wi-moon-alt-waxing-crescent-1.png"
|
||||
elif moon_phase <= 0.071:
|
||||
return "wi-moon-alt-waxing-crescent-2.png"
|
||||
elif moon_phase <= 0.107:
|
||||
return "wi-moon-alt-waxing-crescent-3.png"
|
||||
elif moon_phase <= 0.142:
|
||||
return "wi-moon-alt-waxing-crescent-4.png"
|
||||
elif moon_phase <= 0.178:
|
||||
return "wi-moon-alt-waxing-crescent-5.png"
|
||||
elif moon_phase <= 0.214:
|
||||
return "wi-moon-alt-waxing-crescent-6.png"
|
||||
elif moon_phase <= 0.25:
|
||||
return "wi-moon-alt-first-quarter.png"
|
||||
elif moon_phase <= 0.285:
|
||||
return "wi-moon-alt-waxing-gibbous-1.png"
|
||||
elif moon_phase <= 0.321:
|
||||
return "wi-moon-alt-waxing-gibbous-2.png"
|
||||
elif moon_phase <= 0.357:
|
||||
return "wi-moon-alt-waxing-gibbous-3.png"
|
||||
elif moon_phase <= 0.392:
|
||||
return "wi-moon-alt-waxing-gibbous-4.png"
|
||||
elif moon_phase <= 0.428:
|
||||
return "wi-moon-alt-waxing-gibbous-5.png"
|
||||
elif moon_phase <= 0.464:
|
||||
return "wi-moon-alt-waxing-gibbous-6.png"
|
||||
elif moon_phase <= 0.5:
|
||||
return "wi-moon-alt-full.png"
|
||||
elif moon_phase <= 0.535:
|
||||
return "wi-moon-alt-waning-gibbous-1.png"
|
||||
elif moon_phase <= 0.571:
|
||||
return "wi-moon-alt-waning-gibbous-2.png"
|
||||
elif moon_phase <= 0.607:
|
||||
return "wi-moon-alt-waning-gibbous-3.png"
|
||||
elif moon_phase <= 0.642:
|
||||
return "wi-moon-alt-waning-gibbous-4.png"
|
||||
elif moon_phase <= 0.678:
|
||||
return "wi-moon-alt-waning-gibbous-5.png"
|
||||
elif moon_phase <= 0.714:
|
||||
return "wi-moon-alt-waning-gibbous-6.png"
|
||||
elif moon_phase <= 0.75:
|
||||
return "wi-moon-alt-third-quarter.png"
|
||||
elif moon_phase <= 0.785:
|
||||
return "wi-moon-alt-waning-crescent-1.png"
|
||||
elif moon_phase <= 0.821:
|
||||
return "wi-moon-alt-waning-crescent-2.png"
|
||||
elif moon_phase <= 0.857:
|
||||
return "wi-moon-alt-waning-crescent-3.png"
|
||||
elif moon_phase <= 0.892:
|
||||
return "wi-moon-alt-waning-crescent-4.png"
|
||||
elif moon_phase <= 0.928:
|
||||
return "wi-moon-alt-waning-crescent-5.png"
|
||||
elif moon_phase <= 0.964:
|
||||
return "wi-moon-alt-waning-crescent-6.png"
|
||||
else:
|
||||
return "wi-alien.png"
|
||||
|
||||
|
||||
def get_currently(self, *args, **kwargs):
|
||||
return self.get_weather(*args, **kwargs)["currently"]
|
||||
|
||||
|
||||
def get_hourly(self, *args, **kwargs):
|
||||
return self.get_weather(*args, **kwargs)["hourly"]
|
||||
|
||||
|
||||
def get_daily(self, *args, **kwargs):
|
||||
return self.get_weather(*args, **kwargs)["daily"]
|
||||
|
||||
|
||||
WeatherAPI = DarkSkyAPI
|
||||
|
||||
if __name__ == '__main__':
|
||||
dsa = DarkSkyAPI()
|
||||
print(dsa.get_currently())
|
||||
Loading…
Add table
Add a link
Reference in a new issue