291 lines
9.5 KiB
Python
291 lines
9.5 KiB
Python
# display/image_maker.py
|
|
|
|
from PIL import Image, ImageDraw, ImageFont, ImageOps
|
|
|
|
from fonts import Font
|
|
from icons import Icon
|
|
|
|
from .epd4in2 import EPD_HEIGHT as HEIGHT
|
|
from .epd4in2 import EPD_WIDTH as WIDTH
|
|
|
|
|
|
def closest(num_list: list, K):
|
|
return num_list[min(range(len(num_list)), key=lambda i: abs(num_list[i] - K))]
|
|
|
|
|
|
def draw_temp_block(
|
|
d: ImageDraw.ImageDraw,
|
|
coords: tuple[int, int],
|
|
content: list[tuple[str, int, ImageFont.FreeTypeFont]],
|
|
):
|
|
x, y = coords
|
|
for text, color, font in content:
|
|
d.text((x, y), text, fill=color, font=font)
|
|
text_width = int(font.getlength(text))
|
|
x += text_width
|
|
|
|
|
|
class eInkImage:
|
|
def __init__(self):
|
|
self.info_font_y_offset = 1
|
|
self.info_y_offset = 14
|
|
|
|
def make_image(
|
|
self, date: tuple, weather_data: dict, network_status: bool
|
|
) -> Image.Image:
|
|
forecast = weather_data.get("forecast", {})
|
|
image = Image.new("1", (WIDTH, HEIGHT), 255)
|
|
draw = ImageDraw.Draw(image)
|
|
|
|
# Top
|
|
draw.text((6, 2), date[0], font=Font.f20, fill=0)
|
|
draw.text((6, 20), date[1], font=Font.f60, fill=0)
|
|
|
|
# --- Left-side ---
|
|
image.paste(Icon.default["indoor"], (6, 78 + self.info_y_offset))
|
|
draw.text(
|
|
(36, 78 - self.info_font_y_offset + self.info_y_offset),
|
|
"Inomhus",
|
|
font=Font.f20,
|
|
fill=0,
|
|
)
|
|
|
|
# Temp Logic
|
|
indoor_temp = weather_data.get("indoor_temp", 20)
|
|
if indoor_temp >= 24.0:
|
|
indoor_temp_icon = Icon.default["temp_high"]
|
|
elif indoor_temp <= 18.0:
|
|
indoor_temp_icon = Icon.default["temp_low"]
|
|
else:
|
|
indoor_temp_icon = Icon.default["temp_mid"]
|
|
|
|
image.paste(indoor_temp_icon, (6, 116 + self.info_y_offset))
|
|
|
|
draw_temp_block(
|
|
draw,
|
|
(38, 93 + self.info_y_offset),
|
|
[
|
|
(str(weather_data.get("indoor_temp", "--")), 0, Font.f55),
|
|
("°C", 0, Font.f25),
|
|
],
|
|
)
|
|
# ----------
|
|
image.paste(Icon.default["humidity"], (6, 160 + self.info_y_offset))
|
|
draw.text(
|
|
(36, 160 - self.info_font_y_offset + self.info_y_offset),
|
|
f"{weather_data.get('indoor_humidity', '--')}%",
|
|
font=Font.f20,
|
|
fill=0,
|
|
)
|
|
# ----------
|
|
image.paste(Icon.default["pressure"], (6, 190 + self.info_y_offset))
|
|
draw.text(
|
|
(36, 190 - self.info_font_y_offset + self.info_y_offset),
|
|
f"{weather_data.get('pressure', '--')}hPa",
|
|
font=Font.f20,
|
|
fill=0,
|
|
)
|
|
|
|
# Sun
|
|
image.paste(Icon.default["sunrise"], (6, 220 + self.info_y_offset))
|
|
draw.text(
|
|
(36, 220 - self.info_font_y_offset + self.info_y_offset),
|
|
weather_data.get("sunrise", "--"),
|
|
font=Font.f20,
|
|
fill=0,
|
|
)
|
|
# ----------
|
|
image.paste(Icon.default["sunset"], (101, 220 + self.info_y_offset))
|
|
draw.text(
|
|
(131, 220 - self.info_font_y_offset + self.info_y_offset),
|
|
weather_data.get("sunset", "--"),
|
|
font=Font.f20,
|
|
fill=0,
|
|
)
|
|
|
|
# Moon
|
|
moon_value = closest(
|
|
[direction for direction, _ in Icon.moon.items()],
|
|
weather_data.get("moon_illumination", 0.167) * 100,
|
|
)
|
|
moon_icon = Icon.moon[moon_value]
|
|
|
|
if not weather_data.get("moon_to_full", True):
|
|
image.paste(moon_icon, (6, 250 + self.info_y_offset))
|
|
else:
|
|
image.paste(ImageOps.mirror(moon_icon), (6, 250 + self.info_y_offset))
|
|
draw.text(
|
|
(42, 250 + self.info_y_offset),
|
|
weather_data.get("moon_phase", "----"),
|
|
font=Font.f20,
|
|
fill=0,
|
|
)
|
|
|
|
# Right-side
|
|
right_side_x = 204
|
|
if weather_data.get("temperature", 10) >= 24.0:
|
|
outdoor_temp_icon = Icon.default["temp_high"]
|
|
elif weather_data.get("temperature", 10) < 0.0:
|
|
outdoor_temp_icon = Icon.default["temp_low"]
|
|
else:
|
|
outdoor_temp_icon = Icon.default["temp_mid"]
|
|
image.paste(outdoor_temp_icon, (right_side_x, 116 + self.info_y_offset))
|
|
draw_temp_block(
|
|
draw,
|
|
(right_side_x + 30, 93 + self.info_y_offset),
|
|
[
|
|
(str(weather_data.get("temperature", "--")), 0, Font.f55),
|
|
("°C", 0, Font.f25),
|
|
],
|
|
)
|
|
# ----------
|
|
image.paste(Icon.default["humidity"], (right_side_x, 160 + self.info_y_offset))
|
|
draw.text(
|
|
(right_side_x + 30, 160 - self.info_font_y_offset + self.info_y_offset),
|
|
f"{weather_data.get('humidity', '--')}%",
|
|
font=Font.f20,
|
|
fill=0,
|
|
)
|
|
# ----------
|
|
image.paste(Icon.default["dew"], (right_side_x + 96, 160 + self.info_y_offset))
|
|
draw.text(
|
|
(right_side_x + 126, 160 - self.info_font_y_offset + self.info_y_offset),
|
|
f"{weather_data.get('dew', '--')}°C",
|
|
font=Font.f20,
|
|
fill=0,
|
|
)
|
|
|
|
# Wind
|
|
wind_offset = 20
|
|
wind_deg = forecast.get("wind", {}).get("deg", 0)
|
|
wind_speed = forecast.get("wind", {}).get("speed", "--")
|
|
wind_gust = forecast.get("wind", {}).get("gust", "--")
|
|
has_wind = bool(forecast.get("wind"))
|
|
|
|
wind_value = closest(
|
|
[direction for direction, _ in Icon.wind.items()], wind_deg
|
|
)
|
|
|
|
wind_direction, wind_icon = (
|
|
Icon.wind[wind_value] if has_wind else ("--", Icon.wind[180][1])
|
|
)
|
|
|
|
direction_width = int(Font.f14.getlength(wind_direction))
|
|
wind_direction_text_x = (
|
|
right_side_x + wind_offset + ((30 - direction_width) / 2)
|
|
)
|
|
|
|
image.paste(wind_icon, (right_side_x + wind_offset, 192 + self.info_y_offset))
|
|
image.paste(
|
|
Icon.default["wind"],
|
|
(right_side_x + 40 + wind_offset, 190 + self.info_y_offset),
|
|
)
|
|
image.paste(
|
|
Icon.default["wind_gust"],
|
|
(right_side_x + 40 + wind_offset, 210 + self.info_y_offset),
|
|
)
|
|
|
|
draw.text(
|
|
(wind_direction_text_x, 222 + self.info_y_offset),
|
|
wind_direction,
|
|
font=Font.f14,
|
|
fill=0,
|
|
)
|
|
draw.text(
|
|
(
|
|
right_side_x + 70 + wind_offset,
|
|
190 - self.info_font_y_offset + self.info_y_offset,
|
|
),
|
|
f"{wind_speed}{'m/s' if has_wind else ''}",
|
|
font=Font.f20,
|
|
fill=0,
|
|
)
|
|
draw.text(
|
|
(
|
|
right_side_x + 70 + wind_offset,
|
|
210 - self.info_font_y_offset + self.info_y_offset,
|
|
),
|
|
f"{wind_gust}{'m/s' if has_wind else ''}",
|
|
font=Font.f20,
|
|
fill=0,
|
|
)
|
|
|
|
# UV
|
|
image.paste(Icon.default["uv"], (right_side_x + 60, 240 + self.info_y_offset))
|
|
draw.text(
|
|
(
|
|
right_side_x + 60 + 30,
|
|
240 - self.info_font_y_offset + self.info_y_offset,
|
|
),
|
|
str(weather_data.get("uv_index", "--")),
|
|
font=Font.f20,
|
|
fill=0,
|
|
)
|
|
|
|
# Warnings
|
|
if not network_status:
|
|
image.paste(
|
|
Icon.warning["no_wifi"],
|
|
(WIDTH - Icon.warning["no_wifi"].width - 4, 80),
|
|
)
|
|
|
|
if (
|
|
forecast.get("wind", {}).get("speed", 0) >= 14.0
|
|
or forecast.get("wind", {}).get("gust", 0) >= 16.0
|
|
):
|
|
image.paste(
|
|
Icon.warning["wind"], (250 - Icon.warning["wind"].width - 4, 80)
|
|
)
|
|
|
|
if weather_data.get("fire_index", 0) > 3:
|
|
image.paste(
|
|
Icon.warning["fire"], (WIDTH - Icon.warning["fire"].width - 4, 4)
|
|
)
|
|
|
|
alerts = weather_data.get("weather_alerts")
|
|
|
|
if alerts and isinstance(alerts, list) and len(alerts) > 0:
|
|
warning_code = alerts[0].get("type")
|
|
|
|
if warning_code in ["RED", "ORANGE", "YELLOW"]:
|
|
image.paste(
|
|
Icon.warning[warning_code],
|
|
(WIDTH - Icon.warning[warning_code].width - 4, 80),
|
|
)
|
|
elif warning_code == "MESSAGE":
|
|
if warning_code == "WATER_SHORTAGE":
|
|
image.paste(
|
|
Icon.warning["water_shortage"],
|
|
(WIDTH - Icon.warning["water_shortage"].width - 4, 80),
|
|
)
|
|
elif warning_code == "HIGH_TEMPERATURES":
|
|
image.paste(
|
|
Icon.warning["temperature_high"],
|
|
(WIDTH - Icon.warning["temperature_high"].width - 4, 80),
|
|
)
|
|
else:
|
|
image.paste(
|
|
Icon.warning["YELLOW"],
|
|
(WIDTH - Icon.warning["YELLOW"].width - 4, 80),
|
|
)
|
|
|
|
# Weather-icon
|
|
image = image.convert("L")
|
|
if (
|
|
weather_data.get("sunrise", "06:00")
|
|
< date[1]
|
|
< weather_data.get("sunset", "20:00")
|
|
):
|
|
time_of_day = "day"
|
|
else:
|
|
time_of_day = "night"
|
|
weather_icon = Icon.get_forecast(
|
|
forecast.get("weather_symbol", 0),
|
|
time_of_day,
|
|
forecast.get("wind", {}).get("speed", 0),
|
|
)
|
|
weather_icon_y_offset = int(((120 - weather_icon.size[1]) / 2))
|
|
image.paste(weather_icon, (250, weather_icon_y_offset), weather_icon)
|
|
|
|
return image
|