diff options
| author | Christian Kolset <ckolset@colostate.edu> | 2026-08-10 16:12:05 -0600 |
|---|---|---|
| committer | Christian Kolset <ckolset@colostate.edu> | 2026-08-10 16:12:05 -0600 |
| commit | 5d5bcaed8639a162fda85c8cd0d340c296a054b8 (patch) | |
| tree | a7a1a62fb607c9c85127dab4aab17ccf8e22c906 /ui/control_editor.py | |
| parent | 46231c00994cd681d598961f2056dbda31c3e483 (diff) | |
Unify Signals/Channels around auto-naming, plot-by-channel, and shared vars
Devices: auto-name from OS-reported port description (Device Manager /
udev string) instead of a generic label; default Serial #Channels 1;
consistent simulate-mode checkbox across backends; inline device rename.
Channels: calibration/filters move out of Signals into a Channel
"Operation" (adds Scale/Offset alongside the existing math kinds);
single-physical-source Channels get an embedded Filters block; new
Channels auto-name/auto-ID from their source and reject duplicate
names/IDs on apply; "Create Channel" button on each Signal row; renamed
"Sources" -> "Signals" in the Channels UI; Signals' READ/WRITE dir badge
(was silently-broken IN/OUT) now reflects the real writable flag.
Plot: default layout is one pane per Channel (not per device, not raw
Signals) titled by channel name; "+ Add Channel" is a checkbox dialog
instead of an inline dropdown; traces get up/down reorder; live unit
changes (e.g. a Mark-10 gauge switching lb/kgF/N/ozF) propagate to an
open pane's axis label without clobbering a manually-typed one.
Control Panel: hardware assignment now picks from the same writable-
Signals list instead of a separate device/channel combo with a
hardcoded Arduino pin list; stale visual indicator when a control's
target device/channel disconnects or disappears; documented the `vars`
bridge (Control Panel On Action Script <-> Channel scripts) in both
directions' help text and script templates.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'ui/control_editor.py')
| -rw-r--r-- | ui/control_editor.py | 96 |
1 files changed, 31 insertions, 65 deletions
diff --git a/ui/control_editor.py b/ui/control_editor.py index 111a71f..d167517 100644 --- a/ui/control_editor.py +++ b/ui/control_editor.py @@ -204,31 +204,26 @@ class ControlEditorDialog(QDialog): root.addWidget(id_grp) - # ── Device & channel ──────────────────────────────────────────── + # ── Target signal ──────────────────────────────────────────────── + # Sourced from the same writable-channel set Configuration > Channels > + # Signals shows as "OUT" — the Signals tab is the central place hardware + # endpoints (including button targets) are registered. hw_grp = QGroupBox("Hardware Assignment") hw_form = QFormLayout(hw_grp); hw_form.setContentsMargins(10, 16, 10, 10) - self._dev_cb = QComboBox() - self._dev_cb.addItem("— none —", userData="") + self._target_cb = QComboBox() + self._target_cb.addItem("— none —", userData=("", "")) for dev in self.registry.all_instances(): - label = f"{dev.info.device_id} ({dev.info.name})" - self._dev_cb.addItem(label, userData=dev.info.device_id) - self._dev_cb.currentIndexChanged.connect(self._on_dev_changed) - hw_form.addRow("Device:", self._dev_cb) - - self._ch_cb = QComboBox() - self._ch_cb.setEditable(True) # allow typing custom pin like "D7" - self._ch_cb.setInsertPolicy(QComboBox.InsertPolicy.NoInsert) - hw_form.addRow("Channel / Pin:", self._ch_cb) + for ch in dev.info.channels: + if not ch.enabled or not ch.writable: + continue + label = f"{dev.info.device_id} / {ch.name} ({ch.channel_id})" + self._target_cb.addItem(label, userData=(dev.info.device_id, ch.channel_id)) + hw_form.addRow("Target Signal:", self._target_cb) self._active_low_chk = QCheckBox("Active-low output (ON sends LOW)") hw_form.addRow("Polarity:", self._active_low_chk) - self._ch_hint = QLabel("") - self._ch_hint.setObjectName("traceSource") - self._ch_hint.setWordWrap(True) - hw_form.addRow(self._ch_hint) - root.addWidget(hw_grp) # ── Type-specific params ──────────────────────────────────────── @@ -256,7 +251,10 @@ class ControlEditorDialog(QDialog): _help = QLabel( "Runs every time this control changes value.\n" "Available: value, channel_id, device_id, vars (shared dict), " - "channels (latest values by channel_id), math" + "channels (latest values by channel_id), math\n" + "vars set here are readable from any Channel's script " + "(Configuration > Channels) via vars.get('key', default) — " + "this is how a button feeds a value into Channel math." ) _help.setObjectName("traceSource"); _help.setWordWrap(True) script_lay.addWidget(_help) @@ -316,21 +314,22 @@ class ControlEditorDialog(QDialog): self._type_cb.setCurrentIndex(idx) self._title_edit.setText(self.spec.title) - # Device - for i in range(self._dev_cb.count()): - if self._dev_cb.itemData(i) == self.spec.device_id: - self._dev_cb.setCurrentIndex(i); break - - # Channel (populated after device selection) - self._on_dev_changed() + # Target signal found = False - for i in range(self._ch_cb.count()): - if self._ch_cb.itemData(i) == self.spec.channel_id: - self._ch_cb.setCurrentIndex(i) + target = (self.spec.device_id, self.spec.channel_id) + for i in range(self._target_cb.count()): + if self._target_cb.itemData(i) == target: + self._target_cb.setCurrentIndex(i) found = True break - if not found: - self._ch_cb.setCurrentText(self.spec.channel_id) + if not found and self.spec.device_id: + # Not in the current writable-signal list (e.g. a legacy raw pin + # target, or the channel/device is now disabled/removed) — keep it + # as a synthetic entry so editing this control doesn't silently + # clear its saved hardware assignment. + label = f"{self.spec.device_id} / {self.spec.channel_id} (not currently available)" + self._target_cb.addItem(label, userData=target) + self._target_cb.setCurrentIndex(self._target_cb.count() - 1) self._active_low_chk.setChecked(bool(self.spec.active_low)) # Params @@ -351,50 +350,17 @@ class ControlEditorDialog(QDialog): self._title_edit.text() in CONTROL_TYPES: self._title_edit.setText(type_name) - def _on_dev_changed(self): - self._ch_cb.clear() - dev_id = self._dev_cb.currentData() - if not dev_id: - self._ch_hint.setText("No device selected — type a channel manually") - return - dev = self.registry.get_instance(dev_id) - if not dev: - return - # Add actual channels (skip disabled — can't be driven while switched - # off — and skip read-only channels — a control writes, so a channel - # with no write mapping should never be offered as a target) - for ch in dev.info.channels: - if not ch.enabled or not ch.writable: - continue - self._ch_cb.addItem(f"{ch.name} ({ch.channel_id})", - userData=ch.channel_id) - # For Arduino backends also suggest digital pins for output - if hasattr(dev, "backend") and dev.backend == "arduino": - self._ch_cb.insertSeparator(self._ch_cb.count()) - for pin in ["D5", "D6", "D7", "D8", "D9", "D10", "D11", "D13"]: - self._ch_cb.addItem(f"{pin} (digital out)", userData=pin) - self._ch_hint.setText( - "Analog pins (A0…) for reading.\n" - "Digital pins (D5…) for on/off output.\n" - "D9, D10, D11 support PWM." - ) - else: - self._ch_hint.setText("") - def _on_ok(self): ctype = self._type_cb.currentText() title = self._title_edit.text().strip() or ctype - # Resolve channel_id — prefer userData if it's a real combo item - ch_text = self._ch_cb.currentText().strip() - ch_data = self._ch_cb.currentData() - channel_id = ch_data if ch_data else ch_text.split()[0] # strip "(name)" part + dev_id, channel_id = self._target_cb.currentData() or ("", "") spec = ControlSpec( control_type=ctype, title=title, icon=CONTROL_ICONS.get(ctype, "⚙"), - device_id=self._dev_cb.currentData() or "", + device_id=dev_id, channel_id=channel_id, active_low=self._active_low_chk.isChecked(), on_action_script=self._script_edit.toPlainText().strip(), |
