Files

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