This commit is contained in:
2026-07-09 14:15:38 +03:00
commit 8127789b08
8 changed files with 368 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
# Python
__pycache__/
*.py[cod]
*.pyo
# Editors
.vscode/
.idea/
# Python virtual environments
venv/
.venv/
# Generated files
investments.html*
master.zip
# OS files
.DS_Store
Thumbs.db
+167
View File
@@ -0,0 +1,167 @@
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from time import sleep
from datetime import datetime
import logging
import signal
import sys
from requests import RequestException
from lib.image.rgb565 import convert_rgb_to_rgb565_numpy
from lib.terminal import disable_cursor, enable_cursor, restore_screen, was_key_pressed
from lib.lifedata import get_status_data
SCREEN_WIDTH_PIXELS = 900
SCREEN_HEIGHT_PIXELS = 1440
MARGIN_PIXELS = 15
CLOCK_SIZE_MULTIPLIER = (900 - 2 * MARGIN_PIXELS) / 243
STATUS_SIZE_MULTIPLIER = 2
CLOCK_WIDTH_PIXELS = 243
CLOCK_HEIGHT_PIXELS = 70
def signal_handler(signal, frame):
logging.info('You pressed Ctrl+C!')
restore_screen('/dev/tty1')
enable_cursor('/dev/tty1')
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
#capture also ctrl+c
signal.signal(signal.SIGTERM, signal_handler)
disable_cursor('/dev/tty1')
img = Image.new('RGB', (SCREEN_WIDTH_PIXELS,SCREEN_HEIGHT_PIXELS))
draw = ImageDraw.Draw(img)
def draw_label(label, x, y):
label_font = ImageFont.truetype('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', CLOCK_HEIGHT_PIXELS // 2)
label_text_color = (0, 255//2, 0)
draw.text((x,y), label, font=label_font, fill=label_text_color, anchor='rb')
def draw_clock_digits(x, y, size ,hours, minutes, blink_second, color=(0, 255, 0)):
"""
Draws clock digits at the specified position.
"""
current_time = f'{hours}:{minutes}'
# Blink logic
if ':' in current_time and datetime.now().second % 2 == blink_second:
current_time = current_time.replace(":", " ")
draw_digits(x, y, size, current_time, color)
def draw_digits(x, y, size, digits, color=(0, 255, 0)):
"""
Draws clock digits at the specified position.
"""
font_path = "/usr/share/fonts/truetype/dseg/DSEG14Classic-BoldItalic.ttf"
font = ImageFont.truetype(font_path, int(CLOCK_HEIGHT_PIXELS * size))
draw.text((x, y), digits, font=font, fill=color)
def draw_clock():
x = SCREEN_WIDTH_PIXELS - CLOCK_WIDTH_PIXELS * CLOCK_SIZE_MULTIPLIER - MARGIN_PIXELS
y = MARGIN_PIXELS
now = datetime.now()
hours = now.strftime("%H")
minutes = now.strftime("%M")
draw_clock_digits(x, y, CLOCK_SIZE_MULTIPLIER, hours, minutes, 0)
#draw_label('Time:', x - MARGIN_PIXELS, y + CLOCK_HEIGHT_PIXELS)
def draw_death_clock():
x = SCREEN_WIDTH_PIXELS - CLOCK_WIDTH_PIXELS * STATUS_SIZE_MULTIPLIER - MARGIN_PIXELS
y = MARGIN_PIXELS + CLOCK_HEIGHT_PIXELS * CLOCK_SIZE_MULTIPLIER + 2 * MARGIN_PIXELS
death_date = datetime.strptime("08/23/2026", "%m/%d/%Y").date()
today = datetime.today().date()
days_until_death = (death_date - today).days
color = (0, 255, 0)
draw_digits(x, y, STATUS_SIZE_MULTIPLIER, f'{days_until_death:4}', color)
draw_label('death:', x - MARGIN_PIXELS, y + CLOCK_HEIGHT_PIXELS * STATUS_SIZE_MULTIPLIER)
def draw_journal_minutes(status):
x = SCREEN_WIDTH_PIXELS - CLOCK_WIDTH_PIXELS * STATUS_SIZE_MULTIPLIER - MARGIN_PIXELS
y = MARGIN_PIXELS + CLOCK_HEIGHT_PIXELS * CLOCK_SIZE_MULTIPLIER + CLOCK_HEIGHT_PIXELS * STATUS_SIZE_MULTIPLIER + 4 * MARGIN_PIXELS
elapsed_minutes = status['journal_updated_minutes']
hours = elapsed_minutes // 60
minutes = elapsed_minutes % 60
if elapsed_minutes <=15:
color = (0, 255, 0)
elif elapsed_minutes <= 20:
color = (255, 255, 0)
else:
color = (255, 0, 0)
draw_clock_digits(x, y, STATUS_SIZE_MULTIPLIER, f'{hours:02}', f'{minutes:02}', 1, color)
draw_label('Journal:', x - MARGIN_PIXELS, y + CLOCK_HEIGHT_PIXELS * STATUS_SIZE_MULTIPLIER)
def draw_fasting(status):
x = SCREEN_WIDTH_PIXELS - CLOCK_WIDTH_PIXELS * STATUS_SIZE_MULTIPLIER - MARGIN_PIXELS
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']:
label = 'Breakfast'
date = 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:
label = 'Fasting'
date = datetime.now().replace(hour=16, minute=0, second=0, microsecond=0)
if date > datetime.now():
delta = date - datetime.now()
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)
def clear_status_area():
x = 0
y = MARGIN_PIXELS + CLOCK_HEIGHT_PIXELS * CLOCK_SIZE_MULTIPLIER + 2 * MARGIN_PIXELS
draw.rectangle([x, y, SCREEN_WIDTH_PIXELS, SCREEN_HEIGHT_PIXELS], fill=(0, 0, 0))
while True:
draw_clock()
draw_death_clock()
try:
status = get_status_data()
draw_journal_minutes(status)
# draw_fasting(status)
except RequestException as e:
logging.error(f'Error fetching status data: {e}')
clear_status_area()
rotated_img = img.rotate(90, expand=True)
img_rgb565 = convert_rgb_to_rgb565_numpy(rotated_img)
with open('/dev/fb0', 'wb') as fb:
fb.write(img_rgb565)
img = Image.new('RGB', (SCREEN_WIDTH_PIXELS,SCREEN_HEIGHT_PIXELS))
draw = ImageDraw.Draw(img)
sleep(1)
+24
View File
@@ -0,0 +1,24 @@
import numpy as np
from PIL import Image, ImageDraw, ImageFont
def convert_rgb_to_rgb565_numpy(image):
if image.mode != 'RGB':
raise ValueError('Image must be in RGB mode')
# Convert image to NumPy array
img_array = np.array(image)
# Extract the individual RGB components
r = img_array[:,:,0].astype(np.uint16)
g = img_array[:,:,1].astype(np.uint16)
b = img_array[:,:,2].astype(np.uint16)
# Shift the bits into the correct positions for RGB565
# rgb565_array = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
# Shift the bits into the correct positions for RGB565 using NumPy's bitwise functions
rgb565_array = np.bitwise_or(np.bitwise_or(np.left_shift(np.bitwise_and(r, 0xF8), 8),
np.left_shift(np.bitwise_and(g, 0xFC), 3)),
np.right_shift(b, 3))
# Flatten the array to a 1D array of bytes and return
return rgb565_array.flatten().tobytes()
+9
View File
@@ -0,0 +1,9 @@
import requests
def get_status_data():
url = 'https://life.itguy.ro/lifeapi/status.html'
data = requests.get(url).json()
return data
+34
View File
@@ -0,0 +1,34 @@
import select
import sys
import termios
import tty
def disable_cursor(tty):
with open(tty, 'w') as t:
t.write("\033[?25l")
def enable_cursor(tty):
with open(tty, 'w') as t:
t.write("\033[?25h")
def restore_screen(tty):
with open(tty, 'w') as t:
t.write("\033[?5h\033[?5l")
def was_key_pressed():
# Save the terminal settings
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
# Set the terminal to raw mode to read key press
tty.setraw(sys.stdin.fileno())
# Use select to check if there's any input
rlist, _, _ = select.select([sys.stdin], [], [], 0)
finally:
# Restore the terminal settings
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return bool(rlist)
+72
View File
@@ -0,0 +1,72 @@
import os
import fcntl
import struct
import mmap
from time import sleep
with open('/dev/tty1', 'w') as tty:
tty.write("\033[?25l")
# Constants for the ioctl call
FBIOGET_VSCREENINFO = 0x4600
# Define a function to perform an ioctl call to get screen info
def get_screen_info(fb_device):
# We will get the variable screen info (resolution and bits per pixel)
# The struct for variable screen info is defined in the Linux kernel headers
# as fb_var_screeninfo, which is 56 bytes
screen_info_format = '8I12I16I2I'
screen_info = struct.pack(screen_info_format, *([0] * 38))
screen_info = fcntl.ioctl(fb_device, FBIOGET_VSCREENINFO, screen_info)
return struct.unpack(screen_info_format, screen_info)
# Open the framebuffer device in read-write mode
with open("/dev/fb0", "r+b") as fb:
# Get screen information using ioctl
info = get_screen_info(fb)
xres, yres, bits_per_pixel = info[0], info[1], info[6]
screensize = xres * yres * bits_per_pixel // 8
# Create a memory-mapped area to the framebuffer
fb_bytes = mmap.mmap(fb.fileno(), screensize, mmap.MAP_SHARED, mmap.PROT_WRITE | mmap.PROT_READ)
# Now you can write pixel data to the framebuffer
# Here is an example that just sets all pixels to black
fb_bytes.seek(0) # Go to the beginning of the framebuffer
fb_bytes.write(b'\x00' * screensize)
# Your framebuffer application code here
# Once your program is finished, you can unmap and close the framebuffer device
fb_bytes.close()
import sys
import termios
import atexit
from select import select
# Save the terminal settings
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
atexit.register(lambda: termios.tcsetattr(fd, termios.TCSADRAIN, old_settings))
def wait_for_key_press():
# Disable canonical input and echo
new_settings = termios.tcgetattr(fd)
new_settings[3] = new_settings[3] & ~termios.ICANON & ~termios.ECHO
termios.tcsetattr(fd, termios.TCSADRAIN, new_settings)
# Wait for input
rlist, _, _ = select([sys.stdin], [], [], None)
if rlist:
sys.stdin.read(1)
# Call the function to wait for any key press
wait_for_key_press()
with open('/dev/tty1', 'w') as tty:
tty.write("\033[0;0H")
tty.write("\033[?5h\033[?5l")
tty.write("\033[?25h")
+25
View File
@@ -0,0 +1,25 @@
import math
# Screen dimensions
screen_width_pixels = 1440
screen_height_pixels = 900
screen_diagonal_inches = 19
# Calculate the aspect ratio dimensions
aspect_ratio = 8 / 5
height_inches = math.sqrt((screen_diagonal_inches**2) / (1 + aspect_ratio**2))
width_inches = aspect_ratio * height_inches
# Calculate PPI (Pixels Per Inch)
ppi_width = screen_width_pixels / width_inches
ppi_height = screen_height_pixels / height_inches
ppi = (ppi_width + ppi_height) / 2 # Average the PPI
# Convert PPI to PPCM (Pixels Per Centimeter)
ppcm = ppi / 2.54
# Determine the pixel size for 1 cm tall characters
font_pixel_height = int(ppcm * 1) # 1 cm
# Output the calculated font size in pixels
print(f"The font size for 1 cm tall characters should be approximately {font_pixel_height} pixels.")
+17
View File
@@ -0,0 +1,17 @@
import fcntl
import struct
FBIOGET_VSCREENINFO = 0x4600
with open('/dev/fb0', 'r+b') as fb:
fb_var_screeninfo = '12sL9I'
var_info = fcntl.ioctl(fb, FBIOGET_VSCREENINFO, struct.pack(fb_var_screeninfo, b'', 0, *[0]*9))
xres, yres, xres_virtual, yres_virtual, xoffset, yoffset, bits_per_pixel = struct.unpack('7I', var_info[:28])
print(xres)
print(yres)
print(bits_per_pixel)