mirror of
https://github.com/infinition/Bjorn.git
synced 2026-03-15 08:52:00 +00:00
- Implemented LokiUtils class with GET and POST endpoints for managing scripts, jobs, and payloads. - Added SentinelUtils class with GET and POST endpoints for managing events, rules, devices, and notifications. - Both classes include error handling and JSON response formatting.
200 lines
7.6 KiB
Python
200 lines
7.6 KiB
Python
"""
|
|
Display Layout Engine for multi-size EPD support.
|
|
Provides data-driven layout definitions per display model.
|
|
"""
|
|
import json
|
|
import os
|
|
import logging
|
|
from logger import Logger
|
|
|
|
logger = Logger(name="display_layout.py", level=logging.DEBUG)
|
|
|
|
# Default layout for 122x250 (epd2in13 reference)
|
|
DEFAULT_LAYOUT = {
|
|
"meta": {
|
|
"name": "epd2in13_default",
|
|
"ref_width": 122,
|
|
"ref_height": 250,
|
|
"description": "Default layout for 2.13 inch e-paper display"
|
|
},
|
|
"elements": {
|
|
"title": {"x": 37, "y": 5, "w": 80, "h": 14},
|
|
"wifi_icon": {"x": 3, "y": 3, "w": 12, "h": 12},
|
|
"bt_icon": {"x": 18, "y": 3, "w": 12, "h": 12},
|
|
"usb_icon": {"x": 33, "y": 4, "w": 12, "h": 12},
|
|
"eth_icon": {"x": 48, "y": 4, "w": 12, "h": 12},
|
|
"battery_icon": {"x": 110, "y": 3, "w": 12, "h": 12},
|
|
"stats_row": {"x": 2, "y": 22, "w": 118, "h": 16},
|
|
"status_image": {"x": 3, "y": 52, "w": 15, "h": 15},
|
|
"progress_bar": {"x": 35, "y": 75, "w": 55, "h": 5},
|
|
"ip_text": {"x": 35, "y": 52, "w": 85, "h": 10},
|
|
"status_line1": {"x": 35, "y": 55, "w": 85, "h": 10},
|
|
"status_line2": {"x": 35, "y": 66, "w": 85, "h": 10},
|
|
"comment_area": {"x": 1, "y": 86, "w": 120, "h": 73},
|
|
"main_character": {"x": 25, "y": 100, "w": 70, "h": 65},
|
|
"lvl_box": {"x": 2, "y": 172, "w": 18, "h": 26},
|
|
"cpu_histogram": {"x": 2, "y": 204, "w": 8, "h": 33},
|
|
"mem_histogram": {"x": 12, "y": 204, "w": 8, "h": 33},
|
|
"network_kb": {"x": 101, "y": 170, "w": 20, "h": 26},
|
|
"attacks_count": {"x": 101, "y": 200, "w": 20, "h": 26},
|
|
"frise": {"x": 0, "y": 160, "w": 122, "h": 10},
|
|
"line_top_bar": {"y": 20},
|
|
"line_mid_section": {"y": 51},
|
|
"line_comment_top": {"y": 85},
|
|
"line_bottom_section": {"y": 170}
|
|
},
|
|
"fonts": {
|
|
"title_size": 11,
|
|
"stats_size": 8,
|
|
"status_size": 8,
|
|
"comment_size": 8,
|
|
"lvl_size": 10
|
|
}
|
|
}
|
|
|
|
# Layout for 176x264 (epd2in7)
|
|
LAYOUT_EPD2IN7 = {
|
|
"meta": {
|
|
"name": "epd2in7_default",
|
|
"ref_width": 176,
|
|
"ref_height": 264,
|
|
"description": "Default layout for 2.7 inch e-paper display"
|
|
},
|
|
"elements": {
|
|
"title": {"x": 50, "y": 5, "w": 120, "h": 16},
|
|
"wifi_icon": {"x": 4, "y": 3, "w": 14, "h": 14},
|
|
"bt_icon": {"x": 22, "y": 3, "w": 14, "h": 14},
|
|
"usb_icon": {"x": 40, "y": 4, "w": 14, "h": 14},
|
|
"eth_icon": {"x": 58, "y": 4, "w": 14, "h": 14},
|
|
"battery_icon": {"x": 158, "y": 3, "w": 14, "h": 14},
|
|
"stats_row": {"x": 2, "y": 24, "w": 172, "h": 18},
|
|
"status_image": {"x": 4, "y": 55, "w": 18, "h": 18},
|
|
"progress_bar": {"x": 45, "y": 80, "w": 80, "h": 6},
|
|
"ip_text": {"x": 45, "y": 55, "w": 125, "h": 12},
|
|
"status_line1": {"x": 45, "y": 58, "w": 125, "h": 12},
|
|
"status_line2": {"x": 45, "y": 72, "w": 125, "h": 12},
|
|
"comment_area": {"x": 2, "y": 92, "w": 172, "h": 78},
|
|
"main_character": {"x": 35, "y": 105, "w": 100, "h": 70},
|
|
"lvl_box": {"x": 2, "y": 178, "w": 22, "h": 30},
|
|
"cpu_histogram": {"x": 2, "y": 215, "w": 10, "h": 38},
|
|
"mem_histogram": {"x": 14, "y": 215, "w": 10, "h": 38},
|
|
"network_kb": {"x": 148, "y": 178, "w": 26, "h": 30},
|
|
"attacks_count": {"x": 148, "y": 215, "w": 26, "h": 30},
|
|
"frise": {"x": 50, "y": 170, "w": 90, "h": 10},
|
|
"line_top_bar": {"y": 22},
|
|
"line_mid_section": {"y": 53},
|
|
"line_comment_top": {"y": 90},
|
|
"line_bottom_section": {"y": 176}
|
|
},
|
|
"fonts": {
|
|
"title_size": 13,
|
|
"stats_size": 9,
|
|
"status_size": 9,
|
|
"comment_size": 9,
|
|
"lvl_size": 12
|
|
}
|
|
}
|
|
|
|
# Registry of built-in layouts
|
|
BUILTIN_LAYOUTS = {
|
|
"epd2in13": DEFAULT_LAYOUT,
|
|
"epd2in13_V2": DEFAULT_LAYOUT,
|
|
"epd2in13_V3": DEFAULT_LAYOUT,
|
|
"epd2in13_V4": DEFAULT_LAYOUT,
|
|
"epd2in7": LAYOUT_EPD2IN7,
|
|
}
|
|
|
|
|
|
class DisplayLayout:
|
|
"""Manages display layout definitions with per-element positioning."""
|
|
|
|
def __init__(self, shared_data):
|
|
self.shared_data = shared_data
|
|
self._layout = None
|
|
self._custom_dir = os.path.join(
|
|
getattr(shared_data, 'current_dir', '.'),
|
|
'resources', 'layouts'
|
|
)
|
|
self.load()
|
|
|
|
def load(self):
|
|
"""Load layout for current EPD type. Custom file overrides built-in."""
|
|
epd_type = getattr(self.shared_data, 'epd_type',
|
|
self.shared_data.config.get('epd_type', 'epd2in13_V4')
|
|
if hasattr(self.shared_data, 'config') else 'epd2in13_V4')
|
|
|
|
# Try custom layout file first
|
|
custom_path = os.path.join(self._custom_dir, f'{epd_type}.json')
|
|
if os.path.isfile(custom_path):
|
|
try:
|
|
with open(custom_path, 'r') as f:
|
|
self._layout = json.load(f)
|
|
logger.info(f"Loaded custom layout from {custom_path}")
|
|
return
|
|
except Exception as e:
|
|
logger.error(f"Failed to load custom layout {custom_path}: {e}")
|
|
|
|
# Fallback to built-in
|
|
base = epd_type.split('_')[0] if '_' in epd_type else epd_type
|
|
self._layout = BUILTIN_LAYOUTS.get(epd_type) or BUILTIN_LAYOUTS.get(base) or DEFAULT_LAYOUT
|
|
logger.info(f"Using built-in layout for {epd_type}: {self._layout['meta']['name']}")
|
|
|
|
def get(self, element_name, prop=None):
|
|
"""Get element position dict or specific property.
|
|
|
|
Returns: dict {x, y, w, h} or int value if prop specified.
|
|
Falls back to (0,0) if element not found.
|
|
"""
|
|
elem = self._layout.get('elements', {}).get(element_name, {})
|
|
if prop:
|
|
return elem.get(prop, 0)
|
|
return elem
|
|
|
|
def font_size(self, name):
|
|
"""Get font size by name."""
|
|
return self._layout.get('fonts', {}).get(name, 8)
|
|
|
|
def meta(self):
|
|
"""Get layout metadata."""
|
|
return self._layout.get('meta', {})
|
|
|
|
def ref_size(self):
|
|
"""Get reference dimensions (width, height)."""
|
|
m = self.meta()
|
|
return m.get('ref_width', 122), m.get('ref_height', 250)
|
|
|
|
def all_elements(self):
|
|
"""Return all element definitions."""
|
|
return dict(self._layout.get('elements', {}))
|
|
|
|
def save_custom(self, layout_dict, epd_type=None):
|
|
"""Save a custom layout to disk."""
|
|
if epd_type is None:
|
|
epd_type = getattr(self.shared_data, 'epd_type',
|
|
self.shared_data.config.get('epd_type', 'epd2in13_V4')
|
|
if hasattr(self.shared_data, 'config') else 'epd2in13_V4')
|
|
os.makedirs(self._custom_dir, exist_ok=True)
|
|
path = os.path.join(self._custom_dir, f'{epd_type}.json')
|
|
tmp = path + '.tmp'
|
|
with open(tmp, 'w') as f:
|
|
json.dump(layout_dict, f, indent=2)
|
|
os.replace(tmp, path)
|
|
self._layout = layout_dict
|
|
logger.info(f"Saved custom layout to {path}")
|
|
|
|
def reset_to_default(self, epd_type=None):
|
|
"""Delete custom layout, revert to built-in."""
|
|
if epd_type is None:
|
|
epd_type = getattr(self.shared_data, 'epd_type',
|
|
self.shared_data.config.get('epd_type', 'epd2in13_V4')
|
|
if hasattr(self.shared_data, 'config') else 'epd2in13_V4')
|
|
custom_path = os.path.join(self._custom_dir, f'{epd_type}.json')
|
|
if os.path.isfile(custom_path):
|
|
os.remove(custom_path)
|
|
logger.info(f"Removed custom layout {custom_path}")
|
|
self.load()
|
|
|
|
def to_dict(self):
|
|
"""Export current layout as dict (for API)."""
|
|
return dict(self._layout) if self._layout else {}
|