diff options
Diffstat (limited to 'ui/control_panel.py')
| -rw-r--r-- | ui/control_panel.py | 53 |
1 files changed, 47 insertions, 6 deletions
diff --git a/ui/control_panel.py b/ui/control_panel.py index 7a3e416..6e78e89 100644 --- a/ui/control_panel.py +++ b/ui/control_panel.py @@ -20,6 +20,9 @@ an instance into ControlPanel.add_widget(). from __future__ import annotations from typing import Optional +import math as _math +from typing import Any + from PyQt6.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QSlider, QDoubleSpinBox, QComboBox, @@ -44,7 +47,9 @@ class ControlWidget(QFrame): def __init__(self, title: str, icon: str = "⚙", device_id: str = "", channel_id: str = "", registry: Optional[DeviceRegistry] = None, - active_low: bool = False): + active_low: bool = False, + processor=None, + on_action_script: str = ""): super().__init__() self.title = title self.icon = icon @@ -52,9 +57,20 @@ class ControlWidget(QFrame): self.channel_id = channel_id self.registry = registry self.active_low = active_low + self._processor = processor + self._on_action_fn = None + if on_action_script.strip(): + self._compile_action(on_action_script) self.setObjectName("controlWidget") self._build_frame() + def _compile_action(self, script: str): + try: + code = compile(script, "<control_script>", "exec") + self._on_action_fn = code + except SyntaxError as e: + print(f"[Control '{self.title}'] script syntax error: {e}") + def _logic_level(self, enabled: bool) -> float: """Map logical ON/OFF to electrical level, respecting active-low outputs.""" if self.active_low: @@ -112,6 +128,27 @@ class ControlWidget(QFrame): f"check device type and channel ID") self.value_changed.emit(self.channel_id, value) + if self._on_action_fn is not None: + try: + sv: dict = {} + channels: dict = {} + if self._processor is not None: + with self._processor._lock: + sv = self._processor._script_vars + channels = {ch_id: v + for (_, ch_id), (_, v) in self._processor._latest.items()} + ns: dict = { + "value": value, + "channel_id": self.channel_id, + "device_id": self.device_id, + "vars": sv, + "channels": channels, + "math": _math, + } + exec(self._on_action_fn, ns) + except Exception as e: + print(f"[Control '{self.title}'] script error: {e}") + # ══════════════════════════════════════════════════════════════════════════════ # On/Off Switch @@ -480,9 +517,10 @@ class ControlPanel(QWidget): controls_changed = pyqtSignal() # emitted whenever widgets are added/edited/removed - def __init__(self, registry: DeviceRegistry): + def __init__(self, registry: DeviceRegistry, processor=None): super().__init__() - self.registry = registry + self.registry = registry + self.processor = processor self._widgets: list[ControlWidget] = [] self._specs: list = [] # parallel list of ControlSpec self._build() @@ -560,7 +598,7 @@ class ControlPanel(QWidget): def _add_widget_from_spec(self, spec): """Instantiate a ControlWidget from a ControlSpec and add to panel.""" from ui.control_editor import ControlSpec as CS - widget = _build_widget_from_spec(spec, self.registry) + widget = _build_widget_from_spec(spec, self.registry, self.processor) if widget is None: return wrapper = self._make_wrapper(widget, spec) @@ -641,7 +679,7 @@ class ControlPanel(QWidget): self._widgets.pop(idx); self._specs.pop(idx) # Insert new one at same position - new_widget = _build_widget_from_spec(new_spec, self.registry) + new_widget = _build_widget_from_spec(new_spec, self.registry, self.processor) if new_widget is None: return new_wrapper = self._make_wrapper(new_widget, new_spec) @@ -674,7 +712,8 @@ class ControlPanel(QWidget): # ── Factory — build a ControlWidget from a ControlSpec ───────────────────────── -def _build_widget_from_spec(spec, registry: DeviceRegistry) -> Optional[ControlWidget]: +def _build_widget_from_spec(spec, registry: DeviceRegistry, + processor=None) -> Optional[ControlWidget]: """Instantiate the right ControlWidget subclass from a ControlSpec.""" # Base kwargs — icon is NOT included here; it's passed explicitly below # so subclasses never get a double-value collision. @@ -683,6 +722,8 @@ def _build_widget_from_spec(spec, registry: DeviceRegistry) -> Optional[ControlW channel_id=spec.channel_id, registry=registry, active_low=getattr(spec, "active_low", False), + processor=processor, + on_action_script=getattr(spec, "on_action_script", ""), ) t = spec.control_type ttl = spec.title |
