Compare commits

...

3 Commits

Author SHA1 Message Date
sorin 00592b5e3a events 2026-07-28 00:37:31 +03:00
sorin 8b8d30da27 milestones 2026-07-28 00:17:26 +03:00
sorin 4f5473e0e1 flexible positioning 2026-07-28 00:08:51 +03:00
+100 -43
View File
@@ -1,7 +1,7 @@
import numpy as np import numpy as np
from PIL import Image, ImageDraw, ImageFont from PIL import Image, ImageDraw, ImageFont
from time import sleep from time import sleep
from datetime import datetime from datetime import datetime, time
import logging import logging
import signal import signal
import sys import sys
@@ -58,14 +58,24 @@ def draw_clock_digits(x, y, size ,hours, minutes, blink_second, color=(0, 255, 0
draw_digits(x, y, size, current_time, color) draw_digits(x, y, size, current_time, color)
def draw_digits(x, y, size, digits, color=(0, 255, 0)): def draw_digits(x, y, size, digits, color=(0, 255, 0), anchor='la'):
""" """
Draws clock digits at the specified position. Draws clock digits at the specified position.
""" """
font_path = "/usr/share/fonts/truetype/dseg/DSEG14Classic-BoldItalic.ttf" font_path = "/usr/share/fonts/truetype/dseg/DSEG14Classic-BoldItalic.ttf"
font = ImageFont.truetype(font_path, int(CLOCK_HEIGHT_PIXELS * size)) font = ImageFont.truetype(font_path, int(CLOCK_HEIGHT_PIXELS * size))
draw.text((x, y), digits, font=font, fill=color) draw.text((x, y), digits, font=font, fill=color, anchor=anchor)
def status_clock_position(order):
"""
Computes the x, y position of the status clock at the given stacking order (0-based).
"""
x = SCREEN_WIDTH_PIXELS - CLOCK_WIDTH_PIXELS * STATUS_SIZE_MULTIPLIER - MARGIN_PIXELS
slot_height = CLOCK_HEIGHT_PIXELS * STATUS_SIZE_MULTIPLIER + 2 * MARGIN_PIXELS
y = (MARGIN_PIXELS + CLOCK_HEIGHT_PIXELS * CLOCK_SIZE_MULTIPLIER + 2 * MARGIN_PIXELS
+ order * slot_height)
return x, y
def draw_clock(): def draw_clock():
x = SCREEN_WIDTH_PIXELS - CLOCK_WIDTH_PIXELS * CLOCK_SIZE_MULTIPLIER - MARGIN_PIXELS x = SCREEN_WIDTH_PIXELS - CLOCK_WIDTH_PIXELS * CLOCK_SIZE_MULTIPLIER - MARGIN_PIXELS
@@ -78,22 +88,81 @@ def draw_clock():
#draw_label('Time:', x - MARGIN_PIXELS, y + CLOCK_HEIGHT_PIXELS) #draw_label('Time:', x - MARGIN_PIXELS, y + CLOCK_HEIGHT_PIXELS)
def draw_death_clock(): def draw_countdown(x, y, target_dt, size=STATUS_SIZE_MULTIPLIER):
x = SCREEN_WIDTH_PIXELS - CLOCK_WIDTH_PIXELS * STATUS_SIZE_MULTIPLIER - MARGIN_PIXELS """
y = MARGIN_PIXELS + CLOCK_HEIGHT_PIXELS * CLOCK_SIZE_MULTIPLIER + 2 * MARGIN_PIXELS Draws a countdown/countup to target_dt: green if more than 15 minutes away,
yellow if less than 15 minutes away, red if target_dt is in the past.
death_date = datetime.strptime("08/23/2026", "%m/%d/%Y").date() """
today = datetime.today().date() now = datetime.now()
days_until_death = (death_date - today).days delta_seconds = (target_dt - now).total_seconds()
if delta_seconds >= 0:
color = (0, 255, 0) color = (255, 255, 0) if delta_seconds < 15 * 60 else (0, 255, 0)
else:
draw_digits(x, y, STATUS_SIZE_MULTIPLIER, f'{days_until_death:4}', color) color = (255, 0, 0)
draw_label('death:', x - MARGIN_PIXELS, y + CLOCK_HEIGHT_PIXELS * STATUS_SIZE_MULTIPLIER)
def draw_journal_minutes(status): total = abs(int(delta_seconds))
x = SCREEN_WIDTH_PIXELS - CLOCK_WIDTH_PIXELS * STATUS_SIZE_MULTIPLIER - MARGIN_PIXELS hours, remainder = divmod(total, 3600)
y = MARGIN_PIXELS + CLOCK_HEIGHT_PIXELS * CLOCK_SIZE_MULTIPLIER + CLOCK_HEIGHT_PIXELS * STATUS_SIZE_MULTIPLIER + 4 * MARGIN_PIXELS minutes = remainder // 60
draw_clock_digits(x, y, size, f'{hours:02}', f'{minutes:02}', 1, color)
WORKDAY_EVENTS = [
(7, 0, 'Wake up'),
(8, 0, 'Start working'),
(11, 0, 'Scrum meeting'),
(14, 0, 'Physiotherapy'),
(15, 0, 'Lunch'),
(18, 0, 'Close work day'),
(18, 30, 'Evening walk'),
(22, 0, 'Close the day'),
(23, 0, 'Go to sleep'),
]
def next_workday_event(now=None):
"""
Returns (target_dt, label) for the next event in WORKDAY_EVENTS, or None on weekends.
Once past the last event of the day, keeps returning it (now overdue) until midnight.
"""
now = now or datetime.now()
if now.weekday() >= 5:
return None
today = now.date()
for hour, minute, label in WORKDAY_EVENTS:
dt = datetime.combine(today, time(hour, minute))
if dt > now:
return dt, label
last_hour, last_minute, last_label = WORKDAY_EVENTS[-1]
return datetime.combine(today, time(last_hour, last_minute)), last_label
def draw_next_event(order):
result = next_workday_event()
if result is None:
return
target, label = result
x, y = status_clock_position(order)
draw_countdown(x, y, target, STATUS_SIZE_MULTIPLIER)
draw_label(f'{label}:', x - MARGIN_PIXELS, y + CLOCK_HEIGHT_PIXELS * STATUS_SIZE_MULTIPLIER)
def milestone_countdown(order, target_date, label):
x, y = status_clock_position(order)
right_x = x + CLOCK_WIDTH_PIXELS * STATUS_SIZE_MULTIPLIER
today = datetime.today().date()
days_until = (target_date - today).days
color = (0, 255, 0)
draw_digits(right_x, y, STATUS_SIZE_MULTIPLIER, f'{days_until}', color, anchor='ra')
draw_label(label, x - MARGIN_PIXELS, y + CLOCK_HEIGHT_PIXELS * STATUS_SIZE_MULTIPLIER)
def draw_journal_minutes(status, order):
x, y = status_clock_position(order)
elapsed_minutes = status['journal_updated_minutes'] elapsed_minutes = status['journal_updated_minutes']
@@ -111,31 +180,17 @@ def draw_journal_minutes(status):
draw_label('Journal:', x - MARGIN_PIXELS, y + CLOCK_HEIGHT_PIXELS * STATUS_SIZE_MULTIPLIER) draw_label('Journal:', x - MARGIN_PIXELS, y + CLOCK_HEIGHT_PIXELS * STATUS_SIZE_MULTIPLIER)
def draw_fasting(status): def draw_fasting(status, order):
x = SCREEN_WIDTH_PIXELS - CLOCK_WIDTH_PIXELS * STATUS_SIZE_MULTIPLIER - MARGIN_PIXELS x, y = status_clock_position(order)
y = MARGIN_PIXELS + CLOCK_HEIGHT_PIXELS * CLOCK_SIZE_MULTIPLIER + CLOCK_HEIGHT_PIXELS * STATUS_SIZE_MULTIPLIER * 2 + 6 * MARGIN_PIXELS
if 'fasting' in status and status['fasting']: if 'fasting' in status and status['fasting']:
label = 'Breakfast' label = 'Breakfast'
date = datetime.strptime(status['fast']['projected_end'], "%Y-%m-%d %H:%M:%S") target = datetime.strptime(status['fast']['projected_end'], "%Y-%m-%d %H:%M:%S")
delta = date - datetime.now()
hours = delta.seconds // 3600
minutes = (delta.seconds // 60) % 60
color = (0, 255, 0)
else: else:
label = 'Fasting' label = 'Fasting'
date = datetime.now().replace(hour=16, minute=0, second=0, microsecond=0) target = datetime.now().replace(hour=16, minute=0, second=0, microsecond=0)
if date > datetime.now():
delta = date - datetime.now() draw_countdown(x, y, target, STATUS_SIZE_MULTIPLIER)
color = (0, 255, 0)
else:
delta = datetime.now() - date
color = (255, 0, 0)
hours = delta.seconds // 3600
minutes = (delta.seconds // 60) % 60
draw_clock_digits(x, y, STATUS_SIZE_MULTIPLIER, f'{hours:02}', f'{minutes:02}', 1, color)
draw_label(f'{label}:', x - MARGIN_PIXELS, y + CLOCK_HEIGHT_PIXELS * STATUS_SIZE_MULTIPLIER) draw_label(f'{label}:', x - MARGIN_PIXELS, y + CLOCK_HEIGHT_PIXELS * STATUS_SIZE_MULTIPLIER)
def clear_status_area(): def clear_status_area():
@@ -146,12 +201,14 @@ def clear_status_area():
while True: while True:
draw_clock() draw_clock()
draw_death_clock() draw_next_event(order=2.5)
try: try:
status = get_status_data() status = get_status_data()
draw_journal_minutes(status) draw_journal_minutes(status, order=0)
# draw_fasting(status) draw_fasting(status, order=1)
milestone_countdown(order=5, target_date=datetime.strptime("09/01/2026", "%m/%d/%Y").date(), label='below 108kg:')
milestone_countdown(order=4, target_date=datetime.strptime("12/25/2026", "%m/%d/%Y").date(), label='below 95kg:')
except RequestException as e: except RequestException as e:
logging.error(f'Error fetching status data: {e}') logging.error(f'Error fetching status data: {e}')