diff options
Diffstat (limited to 'devices/serial_device.py')
| -rw-r--r-- | devices/serial_device.py | 65 |
1 files changed, 56 insertions, 9 deletions
diff --git a/devices/serial_device.py b/devices/serial_device.py index 458aec8..fb131aa 100644 --- a/devices/serial_device.py +++ b/devices/serial_device.py @@ -142,7 +142,18 @@ class SerialDevice(BaseDevice): raw = self._layer.read() if not raw: return {} - # Protocol layers already use channel_id keys — pass through. + if self._fmt == "mark10" and hasattr(self._layer, "current_unit"): + # Gauge can be switched between lb/kgF/N/ozF on the device itself — + # keep the channel's unit in sync so new plots/pickers/CSV log + # headers pick up the currently-selected unit instead of a fixed + # default. Does not relabel the axis of an already-open plot pane. + force_ch = self.get_channel("force") + if force_ch is not None: + force_ch.unit = self._layer.current_unit + if self._fmt not in _GENERIC_FORMATS: + # Protocol layers already key by channel_id — pass through untouched. + # (Write-only channels, e.g. CML "M1_VS", never appear in raw — correctly dropped.) + return raw # Generic (ArduinoLayer) may use arbitrary names — remap by position. mapped: Dict[str, float] = {} raw_vals = list(raw.values()) @@ -154,6 +165,8 @@ class SerialDevice(BaseDevice): return mapped def write_channel(self, channel_id: str, value: Any) -> bool: + if self.status not in (DeviceStatus.CONNECTED, DeviceStatus.SIMULATED): + return False return self._layer.write(channel_id, int(value)) def get_config_widget(self) -> QWidget: @@ -163,6 +176,7 @@ class SerialDevice(BaseDevice): cfg: Dict[str, Any] = { "device_type": self.DEVICE_TYPE, "device_id": self.info.device_id, + "name": self.info.name, "port": self._port, "baud_rate": self._baud, "parse_format": self._fmt, @@ -259,6 +273,7 @@ class SerialDevice(BaseDevice): unit=sc.get("unit", ""), min_value=-1e9, max_value=1e9, color=_COLORS[i % len(_COLORS)], + writable=bool(sc.get("write_cmd")), ) for i, sc in enumerate(self._scpi_channels) ] @@ -270,22 +285,28 @@ class SerialDevice(BaseDevice): unit=mc.get("unit", ""), min_value=-1e9, max_value=1e9, color=_COLORS[i % len(_COLORS)], + # Holding registers (FC03) accept writes (FC06); input registers (FC04) don't. + writable=(mc.get("function_code", 0x03) == 0x03), ) for i, mc in enumerate(self._mb_channels) ] elif fmt == "mark10": from api_layers.protocols.mark10 import UNITS as _MARK10_UNITS return [ - ChannelConfig("force", "Force", "N", -5000.0, 5000.0, color=_COLORS[0]), - ChannelConfig("unit_code", "Unit Code", "", 0.0, float(len(_MARK10_UNITS) - 1), color=_COLORS[1]), + ChannelConfig("force", "Force", "N", -5000.0, 5000.0, color=_COLORS[0]), + ChannelConfig("unit_code", "Unit Code", "", 0.0, float(len(_MARK10_UNITS) - 1), color=_COLORS[1]), + # Write-only action channels (no reading — for control buttons) + ChannelConfig("zero", "Zero Gauge", "", 0.0, 1.0, color=_COLORS[2], writable=True), + ChannelConfig("cycle_units", "Cycle Units", "", 0.0, 1.0, color=_COLORS[3], writable=True), ] elif fmt == "cml": _CMD_UNITS = {"TP": "counts", "TV": "counts/s", "TC": "%×10", "TS": "flags"} + _WRITE_UNITS = {"VS": "counts/s", "MA": "counts"} channels = [] color_idx = 0 for motor in self._motors: + mid = motor.get("motor_id", "M1") for cmd in motor.get("read_cmds", ["TP", "TV", "TC"]): - mid = motor.get("motor_id", "M1") channels.append(ChannelConfig( channel_id=f"{mid}_{cmd}", name=f"{mid} {cmd}", @@ -294,6 +315,28 @@ class SerialDevice(BaseDevice): color=_COLORS[color_idx % len(_COLORS)], )) color_idx += 1 + # Write-only setpoint channels (no reading — for control widgets) + for cmd in ("VS", "MA"): + channels.append(ChannelConfig( + channel_id=f"{mid}_{cmd}", + name=f"{mid} {cmd}", + unit=_WRITE_UNITS.get(cmd, ""), + min_value=-1e6, max_value=1e6, + color=_COLORS[color_idx % len(_COLORS)], + writable=True, + )) + color_idx += 1 + # Write-only action channels — motor must be enabled (ME) before VS/MA take effect + for cmd, label in (("ME", "Enable"), ("MD", "Disable"), ("ST", "Stop")): + channels.append(ChannelConfig( + channel_id=f"{mid}_{cmd}", + name=f"{mid} {label}", + unit="", + min_value=0.0, max_value=1.0, + color=_COLORS[color_idx % len(_COLORS)], + writable=True, + )) + color_idx += 1 return channels or [ChannelConfig("M1_TP", "M1 TP", "counts", color=_COLORS[0])] return [] @@ -419,7 +462,11 @@ class SerialConfigWidget(QWidget): self._sim_cb = QComboBox() self._sim_cb.addItems(["Simulate", "Real Hardware"]) self._sim_cb.setCurrentIndex(0 if self.device.simulate else 1) - conn_form.addRow("Mode:", self._sim_cb) + from core.app_settings import is_developer_mode + mode_lbl = QLabel("Mode:") + dev_mode = is_developer_mode() + mode_lbl.setVisible(dev_mode); self._sim_cb.setVisible(dev_mode) + conn_form.addRow(mode_lbl, self._sim_cb) root.addWidget(conn_grp) @@ -793,7 +840,7 @@ class _CMLPanel(QWidget): lay.setContentsMargins(0, 4, 0, 4) lay.setSpacing(6) - grp = QGroupBox("Motors (RS-232: addr=0 / RS-485: addr 1-31)") + grp = QGroupBox("Motors (motor ID 1-31, always sent explicitly)") grp_lay = QVBoxLayout(grp) scroll = QScrollArea() @@ -856,9 +903,9 @@ class _MotorRow(QWidget): lay.addWidget(self._id_edit) self._addr_spin = QSpinBox() - self._addr_spin.setRange(0, 31) - self._addr_spin.setValue(m.get("address", 1)) - self._addr_spin.setToolTip("0 = RS-232 (no prefix), 1-31 = RS-485") + self._addr_spin.setRange(1, 31) + self._addr_spin.setValue(max(1, m.get("address", 1))) + self._addr_spin.setToolTip("CM1-C motor ID — always sent explicitly as \".<id>\"") lay.addWidget(QLabel("Addr:")) lay.addWidget(self._addr_spin) |
