initial
This commit is contained in:
+20
@@ -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
|
||||||
@@ -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)
|
||||||
@@ -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()
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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)
|
||||||
@@ -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")
|
||||||
|
|
||||||
@@ -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.")
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user