mirror of
https://github.com/infinition/Bjorn.git
synced 2026-03-15 08:52:00 +00:00
Add Loki and Sentinel utility classes for web API endpoints
- 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.
This commit is contained in:
199
display_layout.py
Normal file
199
display_layout.py
Normal file
@@ -0,0 +1,199 @@
|
||||
"""
|
||||
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 {}
|
||||
Reference in New Issue
Block a user