adding severance terminal code

main file - lumon.py
subclasses - palette.py, data_bin.py and data.py
images - lumon-logo and lumon-logo-small
mdr.service - systemd file to run on boot
This commit is contained in:
Liz 2025-02-28 19:46:51 -05:00
parent 06d7ef1381
commit 9f557683eb
7 changed files with 1816 additions and 0 deletions

View file

@ -0,0 +1,300 @@
# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries
# SPDX-License-Identifier: MIT
import math
import random
import tkinter as tk
import time
from palette import Palette
class DataNumber:
active_bin = None
@classmethod
def reset_active_bin(cls):
"""Reset the class-level active bin tracker"""
cls.active_bin = None
def __init__(self, x: int, y: int, canvas: tk.Canvas, base_size: int = 35, palette=Palette):
"""
Initialize a data number for macrodata refinement
"""
self.num = random.randint(0, 9)
self.home_x = x
self.home_y = y
self.x = x
self.y = y
self.mouse_offset_x = 0
self.mouse_offset_y = 0
self.palette = palette
self.color = self.palette.FG
self.alpha = 255
self.base_size = base_size
self.size = base_size
self.refined = False
self.bin_it = False
self.bin = None
self.bin_pause_time = 2
self.bin_pause = self.bin_pause_time
self.canvas = canvas
self.text_id = self.canvas.create_text(
self.x, self.y,
text=str(self.num),
font=('Courier', self.size),
fill=self.color,
anchor='center'
)
self.needs_refinement = False
self.wiggle_offset_x = 0
self.wiggle_offset_y = 0
def refine(self, bin_obj=None, bins_list=None):
"""
Mark this number for refinement and assign it to a bin.
"""
if bin_obj is not None:
if bin_obj.is_full():
return False
target_bin = bin_obj
elif bins_list is not None:
target_bin = self.get_non_full_bin_for_position(bins_list)
if target_bin is None:
return False
else:
raise ValueError("Either bin_obj or bins_list must be provided")
self.bin_it = True
if DataNumber.active_bin is None:
DataNumber.active_bin = target_bin
self.bin = target_bin
else:
if DataNumber.active_bin.is_full():
DataNumber.active_bin = target_bin
self.bin = DataNumber.active_bin
return True
def get_non_full_bin_for_position(self, bins_list):
"""
Determine which available bin should open based on the position of this number.
"""
non_full_bins = [bin_obj for bin_obj in bins_list if not bin_obj.is_full()]
if not non_full_bins:
return None
screen_width = self.canvas.winfo_width()
original_bin_index = self.get_bin_index_for_position(screen_width, len(bins_list))
closest_bin = None
min_distance = float('inf')
for bin_obj in non_full_bins:
distance = abs(bin_obj.i - original_bin_index)
if distance < min_distance:
min_distance = distance
closest_bin = bin_obj
return closest_bin
def get_bin_index_for_position(self, screen_width, num_bins):
"""
Get the bin index that corresponds to this number's position
"""
bin_width = screen_width / num_bins
bin_index = int(self.x / bin_width)
bin_index = max(0, min(bin_index, num_bins - 1))
return bin_index
def go_bin(self):
"""Move toward the bin for refinement"""
if self.bin:
self.bin.open()
if self.bin_pause <= 0:
dx = self.bin.x - self.x
dy = self.bin.y - self.y
distance = math.sqrt(dx*dx + dy*dy)
if distance < 20:
self.alpha = int(255 * (distance / 20))
if distance < 3:
self.wiggle_offset_x = 0
self.wiggle_offset_y = 0
self.mouse_offset_x = 0
self.mouse_offset_y = 0
self.bin.add_number()
self.reset()
return
easing = max(0.03, min(0.1, 5.0 / distance))
self.x += dx * easing
self.y += dy * easing
fade_start_distance = self.distance(self.home_x, self.home_y,
self.bin.x, self.bin.y) * 0.4
current_distance = self.distance(self.x, self.y, self.bin.x, self.bin.y)
if distance >= 20:
self.alpha = self.map_value(current_distance, fade_start_distance, 20, 255, 55)
self.update_display()
if hasattr(self.bin, 'level_elements'):
for element_id in self.bin.level_elements.values():
self.canvas.tag_raise(self.text_id, element_id)
self.bin.last_refined_time = int(time.time() * 1000)
else:
self.bin_pause -= 1
if self.bin_pause > 0:
pulse_size = self.base_size * (1.0 + 0.5 *
(1.0 - (self.bin_pause / self.bin_pause_time)))
self.set_size(pulse_size)
if hasattr(self.bin, 'level_elements'):
for element_id in self.bin.level_elements.values():
self.canvas.tag_raise(self.text_id, element_id)
def reset(self):
"""Reset the number after being binned."""
self.num = random.randint(0, 9)
self.x = self.home_x
self.y = self.home_y
self.wiggle_offset_x = 0
self.wiggle_offset_y = 0
self.mouse_offset_x = 0
self.mouse_offset_y = 0
self.refined = False
self.bin_it = False
self.bin = None
self.color = self.palette.FG
self.alpha = 255
self.bin_pause = self.bin_pause_time
self.update_display()
still_active = False
if not still_active and DataNumber.active_bin is not None:
DataNumber.active_bin = None
def go_home(self):
"""Move the number back to its home position with easing."""
self.x = self.lerp(self.x, self.home_x, 0.1)
self.y = self.lerp(self.y, self.home_y, 0.1)
self.size = self.lerp(self.size, self.base_size, 0.1)
self.update_display()
def set_size(self, sz):
"""Set the size of the number."""
self.size = sz
self.update_display()
def turn(self, new_color):
"""Change the color of the number."""
self.color = new_color
self.update_display()
def inside(self, x1, y1, x2, y2):
"""Check if this number is inside the given rectangle."""
return (
self.x > min(x1, x2) and
self.x < max(x1, x2) and
self.y > min(y1, y2) and
self.y < max(y1, y2)
)
def show(self):
"""Update the display of this number."""
self.update_display()
def update_display(self):
"""Update the text display with current properties and improved alpha handling"""
if self.bin_it:
digit_size = self.lerp(self.size, self.size * 2.5,
self.map_value(self.bin_pause, self.bin_pause_time, 0, 0, 1))
else:
digit_size = self.size
font = ('Courier', int(digit_size))
clamped_alpha = max(0, min(255, self.alpha))
if clamped_alpha == 0:
self.canvas.itemconfig(self.text_id, state='hidden')
return
else:
self.canvas.itemconfig(self.text_id, state='normal')
if clamped_alpha < 255:
bg_color = self.palette.BG
fg_color = self.color
alpha_ratio = clamped_alpha / 255.0
if alpha_ratio < 0.05:
display_color = self.blend_colors(bg_color, fg_color, 0.05)
else:
display_color = self.blend_colors(bg_color, fg_color, alpha_ratio)
else:
display_color = self.color
self.canvas.itemconfig(self.text_id,
text=str(self.num),
font=font,
fill=display_color)
if not hasattr(self, 'wiggle_offset_x'):
self.wiggle_offset_x = 0
if not hasattr(self, 'wiggle_offset_y'):
self.wiggle_offset_y = 0
if not hasattr(self, 'mouse_offset_x'):
self.mouse_offset_x = 0
if not hasattr(self, 'mouse_offset_y'):
self.mouse_offset_y = 0
smooth_wiggle_x = round(self.wiggle_offset_x * 10) / 10
smooth_wiggle_y = round(self.wiggle_offset_y * 10) / 10
smooth_mouse_x = round(self.mouse_offset_x * 10) / 10
smooth_mouse_y = round(self.mouse_offset_y * 10) / 10
display_x = self.x + smooth_wiggle_x + smooth_mouse_x
display_y = self.y + smooth_wiggle_y + smooth_mouse_y
self.canvas.coords(self.text_id, display_x, display_y)
def resize(self, new_x, new_y):
"""Update the home position when the window is resized."""
self.home_x = new_x
self.home_y = new_y
def show_wiggle(self, proximity_factor=0):
"""Make the number threatening"""
if self.needs_refinement and not self.bin_it:
original_x, original_y = self.x, self.y
smooth_x = round(self.wiggle_offset_x * 10) / 10
smooth_y = round(self.wiggle_offset_y * 10) / 10
self.x += smooth_x
self.y += smooth_y
original_color = self.color
base_pulse = 0.7
wave1 = math.sin(time.time() * 0.9) * 0.15
wave2 = math.sin(time.time() * 1.8) * 0.05
highlight_intensity = base_pulse + wave1 + wave2 + (proximity_factor * 0.2)
highlight_intensity = max(0.6, min(1.0, highlight_intensity))
if highlight_intensity > 0.82:
self.color = self.palette.SELECT
else:
blend_amount = (highlight_intensity - 0.6) / 0.22
self.color = self.blend_colors(self.palette.FG, self.palette.SELECT, blend_amount)
self.update_display()
self.x, self.y = original_x, original_y
self.color = original_color
@staticmethod
def lerp(start, end, amt):
"""Linear interpolation between start and end by amt."""
return start + (end - start) * amt
@staticmethod
def map_value(value, start1, stop1, start2, stop2):
"""Re-maps a number from one range to another."""
if stop1 == start1:
return start2
return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1))
@staticmethod
def distance(x1, y1, x2, y2):
"""Calculate distance between two points."""
return math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
@staticmethod
def hex_to_rgb(hex_color):
"""Convert hex color to RGB values."""
# Strip the # if it exists
hex_color = hex_color.lstrip('#')
return tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
@staticmethod
def rgb_to_hex(rgb):
"""Convert RGB tuple to hex color."""
return f"#{rgb[0]:02x}{rgb[1]:02x}{rgb[2]:02x}"
@staticmethod
def blend_colors(color1, color2, ratio):
"""Blend two colors based on ratio (0-1)."""
c1 = DataNumber.hex_to_rgb(color1)
c2 = DataNumber.hex_to_rgb(color2)
blended = tuple(int(c1[i] + (c2[i] - c1[i]) * ratio) for i in range(3))
return DataNumber.rgb_to_hex(blended)

View file

@ -0,0 +1,417 @@
# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries
# SPDX-License-Identifier: MIT
import math
import time
import random
import tkinter as tk
from PIL import Image, ImageTk, ImageFont, ImageDraw
# pylint: disable=broad-exception-caught,too-many-locals
class Bin:
KEYS = ['WO', 'FC', 'DR', 'MA']
MAX_LID_ANGLE = 45
CLOSED_LID_ANGLE = 180
MAX_SHOW_TIME = 1500 # milliseconds
LID_OPEN_CLOSE_TIME = 750 # milliseconds
def __init__(self, width, index, goal, canvas, levels=None, palette=None):
self.w = width
self.i = index
self.x = index * width + width * 0.5
self.canvas = canvas
self.buffer = 60
self.y = canvas.winfo_height() - 50
self.palette = palette
self.fg_color = self.palette.FG
self.bg_color = self.palette.BG
self.goal = goal
self.level_goal = self.goal / 4
self.level_h = self.buffer * 1.7
self.levels_y_offset = self.level_h
self.last_refined_time = self.get_millis()
if levels is None:
self.levels = {
'WO': 0,
'FC': 0,
'DR': 0,
'MA': 0
}
else:
self.levels = levels
self.count = sum(self.levels.values())
self.show_levels = False
self.closing_animation = False
self.opening_animation = False
self.lid_angle = self.CLOSED_LID_ANGLE
self.show_time = 0
self.animation_start_time = 0
self.animation_progress = 0
self.visual_elements = {}
self.level_elements = {}
self.progress_bar_elements = {}
self.create_visual_elements()
def create_outlined_text(self, text, font_size=22, stroke_width=4):
font = ImageFont.truetype("/usr/share/fonts/truetype/msttcorefonts/arial.ttf", font_size)
dummy_img = Image.new("RGBA", (1, 1), (0, 0, 0, 0))
dummy_draw = ImageDraw.Draw(dummy_img)
bbox = dummy_draw.textbbox((0, 0), text, font=font)
text_width = bbox[2] - bbox[0]
text_height = bbox[3] - bbox[1]
padding = 12
width = text_width + padding * 2 + stroke_width * 2
height = text_height + padding * 2 + stroke_width * 2
img = Image.new("RGBA", (width, height), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
fill_color = self.bg_color
stroke_color = self.fg_color
position = (padding + stroke_width, padding+stroke_width)
draw.text(position, text, font=font, fill=fill_color,
stroke_width=stroke_width, stroke_fill=stroke_color)
photo = ImageTk.PhotoImage(img)
return photo
def create_visual_elements(self):
for key in list(self.visual_elements.keys()):
try:
self.canvas.delete(self.visual_elements[key])
except Exception:
pass
for key in list(self.level_elements.keys()):
try:
self.canvas.delete(self.level_elements[key])
except Exception:
pass
for key in list(self.progress_bar_elements.keys()):
try:
self.canvas.delete(self.progress_bar_elements[key])
except Exception:
pass
self.visual_elements = {}
self.level_elements = {}
self.progress_bar_elements = {}
rw = self.w
popup_width = rw
popup_height = self.buffer * 3
base_y = self.y - self.buffer/4
popup_y = base_y - popup_height/2
self.visual_elements['main'] = self.canvas.create_rectangle(
self.x - rw/2, self.y - self.buffer/4,
self.x + rw/2, self.y + self.buffer/4,
outline=self.fg_color, fill=self.bg_color, width=1
)
self.visual_elements['label'] = self.canvas.create_text(
self.x, self.y,
text=f"{self.i:02d}",
font=('Arial', 16),
fill=self.fg_color
)
self.level_elements['popup_bg'] = self.canvas.create_rectangle(
self.x - popup_width/2, popup_y - popup_height/2,
self.x + popup_width/2, popup_y + popup_height/2,
outline="", fill=self.bg_color,
state='hidden'
)
self.level_elements['container'] = self.canvas.create_rectangle(
self.x - popup_width/2, popup_y - popup_height/2,
self.x + popup_width/2, popup_y + popup_height/2,
outline=self.fg_color, fill="", width=1,
state='hidden'
)
bar_height = popup_height * 0.15
for i, key in enumerate(self.KEYS):
level_y = popup_y - popup_height * 0.3 + i * (popup_height * 0.6 / 3)
self.level_elements[f'{key}_label'] = self.canvas.create_text(
self.x - popup_width * 0.4, level_y,
text=key,
font=('Courier', 14),
fill=self.fg_color,
anchor='w',
state='hidden'
)
self.level_elements[f'{key}_outline'] = self.canvas.create_rectangle(
self.x - popup_width * 0.2, level_y - bar_height/2,
self.x + popup_width * 0.4, level_y + bar_height/2,
outline=self.fg_color, fill="",
state='hidden'
)
self.level_elements[f'{key}_progress'] = self.canvas.create_rectangle(
self.x - popup_width * 0.2, level_y - bar_height/2,
self.x - popup_width * 0.2, level_y + bar_height/2,
outline="", fill=self.fg_color,
state='hidden'
)
left_edge_x = self.x - rw/2
right_edge_x = self.x + rw/2
top_edge_y = self.y - self.buffer/4
self.level_elements['left_lid'] = self.canvas.create_line(
left_edge_x, top_edge_y,
self.x, top_edge_y,
fill=self.fg_color, width=1,
state='normal'
)
self.level_elements['right_lid'] = self.canvas.create_line(
self.x, top_edge_y,
right_edge_x, top_edge_y,
fill=self.fg_color, width=1,
state='normal'
)
progress_bar_y = self.y + self.buffer/4 + 2
progress_bar_height = self.buffer/2
self.progress_bar_elements['outline'] = self.canvas.create_rectangle(
self.x - rw/2, progress_bar_y,
self.x + rw/2, progress_bar_y + progress_bar_height,
outline=self.fg_color, fill=self.bg_color, width=1
)
self.progress_bar_elements['fill'] = self.canvas.create_rectangle(
self.x - rw/2 + 1, progress_bar_y + 1,
self.x - rw/2 + 1, progress_bar_y + progress_bar_height - 1,
outline="", fill=self.fg_color
)
percentage_text = "0%"
outlined_img = self.create_outlined_text(percentage_text, font_size=14, stroke_width=1)
left_edge = self.x - rw/2
text_padding = 14
self.progress_bar_elements['text'] = self.canvas.create_image(
left_edge + text_padding, progress_bar_y + progress_bar_height/2,
image=outlined_img,
anchor=tk.CENTER
)
self.fix_z_order()
self.update_progress_bar()
def fix_z_order(self):
self.canvas.tag_raise(self.level_elements['popup_bg'])
self.canvas.tag_raise(self.level_elements['container'])
for key in self.KEYS:
if f'{key}_label' in self.level_elements:
self.canvas.tag_raise(self.level_elements[f'{key}_label'])
if f'{key}_outline' in self.level_elements:
self.canvas.tag_raise(self.level_elements[f'{key}_outline'])
if f'{key}_progress' in self.level_elements:
self.canvas.tag_raise(self.level_elements[f'{key}_progress'])
self.canvas.tag_raise(self.visual_elements['main'])
self.canvas.tag_raise(self.visual_elements['label'])
self.canvas.tag_raise(self.level_elements['left_lid'])
self.canvas.tag_raise(self.level_elements['right_lid'])
self.canvas.tag_raise(self.progress_bar_elements['outline'])
self.canvas.tag_raise(self.progress_bar_elements['fill'])
self.canvas.tag_raise(self.progress_bar_elements['text'])
def is_full(self):
total_levels = sum(self.levels.values())
return total_levels >= self.goal
def add_number(self):
if self.is_full():
return False
options = [key for key in self.KEYS if self.levels[key] < self.level_goal]
if options:
key = random.choice(options)
self.levels[key] += 1
self.open()
self.last_refined_time = self.get_millis()
self.update_display()
self.update_progress_bar()
self.fix_z_order()
return True
return False
def open(self):
if not self.show_levels and not self.opening_animation:
self.animation_start_time = self.get_millis()
self.opening_animation = True
self.closing_animation = False
self.show_levels = True
def update(self):
current_time = self.get_millis()
if self.opening_animation:
elapsed = current_time - self.animation_start_time
if elapsed >= self.LID_OPEN_CLOSE_TIME:
self.opening_animation = False
self.animation_progress = 1.0
else:
progress = elapsed / self.LID_OPEN_CLOSE_TIME
self.animation_progress = 1 - (1 - progress) * (1 - progress)
self.update_display()
self.fix_z_order()
elif self.show_levels and not self.closing_animation:
if current_time - self.last_refined_time > self.MAX_SHOW_TIME:
self.closing_animation = True
self.animation_start_time = current_time
elif self.closing_animation:
elapsed = current_time - self.animation_start_time
if elapsed >= self.LID_OPEN_CLOSE_TIME:
self.closing_animation = False
self.show_levels = False
self.animation_progress = 0.0
else:
progress = elapsed / self.LID_OPEN_CLOSE_TIME
self.animation_progress = 1.0 - (progress * progress)
self.update_display()
self.fix_z_order()
self.update_progress_bar()
def update_progress_bar(self):
total_levels = sum(self.levels.values())
completion_percentage = (total_levels / self.goal) * 100 if self.goal > 0 else 0
rw = self.w
progress_bar_y = self.y + self.buffer/4 + 2
progress_bar_height = self.buffer/2
fill_width = (rw * completion_percentage) / 100
if completion_percentage == 0:
self.canvas.coords(
self.progress_bar_elements['fill'],
self.x - rw/2 + 1, progress_bar_y + 1,
self.x - rw/2 + 1, progress_bar_y + progress_bar_height - 1
)
else:
self.canvas.coords(
self.progress_bar_elements['fill'],
self.x - rw/2 + 1, progress_bar_y + 1,
self.x - rw/2 + max(1, fill_width), progress_bar_y + progress_bar_height - 1
)
percentage_text = f"{int(completion_percentage)}%"
outlined_img = self.create_outlined_text(
percentage_text,
font_size=14,
stroke_width=1
)
left_edge = self.x - rw/2
text_padding = 30 if completion_percentage >= 100 else 24
self.canvas.itemconfig(self.progress_bar_elements['text'], image=outlined_img)
self.canvas.coords(
self.progress_bar_elements['text'],
left_edge + text_padding, progress_bar_y + progress_bar_height/2
)
def update_display(self):
self.count = sum(self.levels.values())
self.count = min(max(self.count, 0), self.goal)
rw = self.w
popup_width = rw
popup_height = self.buffer * 3
base_y = self.y - self.buffer/4
popup_y_closed = base_y
popup_y_open = base_y - popup_height/2
current_popup_y = self.map_value(
self.animation_progress,
0, 1,
popup_y_closed, popup_y_open
)
self.canvas.coords(
self.level_elements['popup_bg'],
self.x - popup_width/2, current_popup_y - popup_height/2,
self.x + popup_width/2, current_popup_y + popup_height/2
)
self.canvas.coords(
self.level_elements['container'],
self.x - popup_width/2, current_popup_y - popup_height/2,
self.x + popup_width/2, current_popup_y + popup_height/2
)
left_edge_x = self.x - rw/2
right_edge_x = self.x + rw/2
top_edge_y = base_y
max_lid_angle = 120
current_angle = self.animation_progress * max_lid_angle
angle_rad = math.radians(current_angle)
left_lid_end_x = left_edge_x + (rw/2) * math.cos(angle_rad)
left_lid_end_y = top_edge_y - (rw/2) * math.sin(angle_rad)
right_lid_end_x = right_edge_x - (rw/2) * math.cos(angle_rad)
right_lid_end_y = top_edge_y - (rw/2) * math.sin(angle_rad)
self.canvas.coords(
self.level_elements['left_lid'],
left_edge_x, top_edge_y,
left_lid_end_x, left_lid_end_y
)
self.canvas.coords(
self.level_elements['right_lid'],
right_edge_x, top_edge_y,
right_lid_end_x, right_lid_end_y
)
visibility_threshold = 0.05
state = 'normal' if self.animation_progress > visibility_threshold else 'hidden'
self.canvas.itemconfig(self.level_elements['popup_bg'], state=state)
self.canvas.itemconfig(self.level_elements['container'], state=state)
self.canvas.itemconfig(self.level_elements['left_lid'], state='normal')
self.canvas.itemconfig(self.level_elements['right_lid'], state='normal')
self.update_level_displays(current_popup_y, popup_height, popup_width)
if state == 'hidden':
for key in self.KEYS:
if f'{key}_label' in self.level_elements:
self.canvas.itemconfig(self.level_elements[f'{key}_label'], state='hidden')
if f'{key}_outline' in self.level_elements:
self.canvas.itemconfig(self.level_elements[f'{key}_outline'], state='hidden')
if f'{key}_progress' in self.level_elements:
self.canvas.itemconfig(self.level_elements[f'{key}_progress'], state='hidden')
def update_level_displays(self, popup_y, popup_height, popup_width):
bar_height = popup_height * 0.15
state = 'normal' if self.animation_progress > 0.05 else 'hidden'
bar_width = popup_width * 0.6
for i, key in enumerate(self.KEYS):
level_y = popup_y - popup_height * 0.3 + i * (popup_height * 0.6 / 3)
self.canvas.coords(
self.level_elements[f'{key}_label'],
self.x - popup_width * 0.4, level_y
)
self.canvas.coords(
self.level_elements[f'{key}_outline'],
self.x - popup_width * 0.2, level_y - bar_height/2,
self.x + popup_width * 0.4, level_y + bar_height/2
)
progress_width = ((bar_width * self.levels[key])
/ self.level_goal if self.level_goal > 0 else 0)
self.canvas.coords(
self.level_elements[f'{key}_progress'],
self.x - popup_width * 0.2, level_y - bar_height/2,
self.x - popup_width * 0.2 + progress_width, level_y + bar_height/2
)
self.canvas.itemconfig(self.level_elements[f'{key}_label'], state=state)
self.canvas.itemconfig(self.level_elements[f'{key}_outline'], state=state)
self.canvas.itemconfig(self.level_elements[f'{key}_progress'], state=state)
def resize(self, new_w):
self.w = new_w
self.x = self.i * new_w + new_w * 0.5
self.y = self.canvas.winfo_height() - 50
for key in list(self.visual_elements.keys()):
try:
self.canvas.delete(self.visual_elements[key])
except Exception:
pass
for key in list(self.level_elements.keys()):
try:
self.canvas.delete(self.level_elements[key])
except Exception:
pass
for key in list(self.progress_bar_elements.keys()):
try:
self.canvas.delete(self.progress_bar_elements[key])
except Exception:
pass
self.visual_elements = {}
self.level_elements = {}
self.progress_bar_elements = {}
self.create_visual_elements()
self.update_display()
@staticmethod
def get_millis():
return int(time.time() * 1000)
@staticmethod
def map_value(value, start1, stop1, start2, stop2):
if stop1 == start1:
return start2
return start2 + (stop2 - start2) * ((value - start1) / (stop1 - start1))

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,18 @@
[Unit]
Description=Macrodata Refinement
After=multi-user.service
[Service]
Type=simple
PAMName=login
User=pi-lumon
Group=pi-lumon
WorkingDirectory=/home/pi-lumon
Environment=DISPLAY=:0
Environment=XAUTHORITY=/home/pi-lumon/.Xauthority
ExecStart=/home/pi-lumon/lumon/bin/python /home/pi-lumon/lumon.py
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target

View file

@ -0,0 +1,9 @@
# SPDX-FileCopyrightText: 2025 Liz Clark for Adafruit Industries
# SPDX-License-Identifier: MIT
class Palette:
"""Color palette based on the Severance MDR terminal"""
def __init__(self):
self.BG = '#010A13' # Dark blue-black background
self.FG = '#ABFFE9' # Light cyan text
self.SELECT = '#ABFFE9' # Selection color