summaryrefslogtreecommitdiff
path: root/devices
diff options
context:
space:
mode:
Diffstat (limited to 'devices')
-rw-r--r--devices/analog_input.py3
-rw-r--r--devices/arduino_device.py4
-rw-r--r--devices/base_device.py1
-rw-r--r--devices/digital_io.py4
-rw-r--r--devices/nidaqmx_device.py4
-rw-r--r--devices/serial_device.py65
6 files changed, 72 insertions, 9 deletions
diff --git a/devices/analog_input.py b/devices/analog_input.py
index c9e5d2a..8938bf2 100644
--- a/devices/analog_input.py
+++ b/devices/analog_input.py
@@ -175,6 +175,7 @@ class AnalogInputDevice(BaseDevice):
return {
"device_type": self.DEVICE_TYPE,
"device_id": self.info.device_id,
+ "name": self.info.name,
"num_channels": self._num_channels,
"simulate": self.simulate,
"backend": self.backend,
@@ -250,6 +251,8 @@ class AnalogInputConfigWidget(QWidget):
self.sim_check = QCheckBox("Simulation Mode (no hardware)")
self.sim_check.setChecked(self.device.simulate)
+ from core.app_settings import is_developer_mode
+ self.sim_check.setVisible(is_developer_mode())
be_form.addRow(self.sim_check)
root.addWidget(be_grp)
diff --git a/devices/arduino_device.py b/devices/arduino_device.py
index 7c1d21b..2f5e8e6 100644
--- a/devices/arduino_device.py
+++ b/devices/arduino_device.py
@@ -108,6 +108,7 @@ class ArduinoDevice(BaseDevice):
channels.append(ChannelConfig(
channel_id=f"do{i}", name=f"DO {i}", unit="",
min_value=0.0, max_value=1.0,
+ writable=True,
color=_DO_COLORS[i % len(_DO_COLORS)],
))
@@ -203,6 +204,7 @@ class ArduinoDevice(BaseDevice):
return {
"device_type": self.DEVICE_TYPE,
"device_id": self.info.device_id,
+ "name": self.info.name,
"analog_pins": self._analog_pins,
"di_pins": self._di_pins,
"do_pins": self._do_pins,
@@ -287,6 +289,8 @@ class ArduinoConfigWidget(QWidget):
self.sim_chk = QCheckBox("Simulation Mode (no hardware)")
self.sim_chk.setChecked(self.device.simulate)
+ from core.app_settings import is_developer_mode
+ self.sim_chk.setVisible(is_developer_mode())
ser_form.addRow(self.sim_chk)
scan_row = QHBoxLayout()
diff --git a/devices/base_device.py b/devices/base_device.py
index aa9f7f2..9803867 100644
--- a/devices/base_device.py
+++ b/devices/base_device.py
@@ -30,6 +30,7 @@ class ChannelConfig:
enabled: bool = True
is_output: bool = False
color: str = "#00d4ff"
+ writable: bool = False # True if this channel accepts write_channel() calls
extra: Dict[str, Any] = field(default_factory=dict)
diff --git a/devices/digital_io.py b/devices/digital_io.py
index 53395dc..7cda61f 100644
--- a/devices/digital_io.py
+++ b/devices/digital_io.py
@@ -60,6 +60,7 @@ class DigitalIODevice(BaseDevice):
channel_id=f"do{i}", name=f"DO {i}", unit="",
min_value=0.0, max_value=1.0,
color=_OUT_COLORS[i % len(_OUT_COLORS)],
+ writable=True,
))
info = DeviceInfo(
@@ -258,6 +259,7 @@ class DigitalIODevice(BaseDevice):
return {
"device_type": self.DEVICE_TYPE,
"device_id": self.info.device_id,
+ "name": self.info.name,
"num_inputs": self._num_inputs,
"num_outputs": self._num_outputs,
"simulate": self.simulate,
@@ -290,6 +292,8 @@ class DigitalIOConfigWidget(QWidget):
be_form.addRow("Backend:", self.be_cb)
self.sim_chk = QCheckBox("Simulate")
self.sim_chk.setChecked(self.device.simulate)
+ from core.app_settings import is_developer_mode
+ self.sim_chk.setVisible(is_developer_mode())
be_form.addRow(self.sim_chk)
self.ni_dev_edit = QLineEdit(self.device._ni_device)
diff --git a/devices/nidaqmx_device.py b/devices/nidaqmx_device.py
index 88bb932..9ebec0f 100644
--- a/devices/nidaqmx_device.py
+++ b/devices/nidaqmx_device.py
@@ -69,6 +69,7 @@ class NidaqmxDevice(BaseDevice):
channel_id=f"do{i}", name=f"DO {i}", unit="",
min_value=0.0, max_value=1.0,
color=_DO_COLORS[i % len(_DO_COLORS)],
+ writable=True,
))
info = DeviceInfo(
@@ -219,6 +220,7 @@ class NidaqmxDevice(BaseDevice):
return {
"device_type": self.DEVICE_TYPE,
"device_id": self.info.device_id,
+ "name": self.info.name,
"num_analog": self._num_analog,
"min_v": self._min_v,
"max_v": self._max_v,
@@ -294,6 +296,8 @@ class NidaqmxConfigWidget(QWidget):
self.sim_chk = QCheckBox("Simulation Mode (no hardware)")
self.sim_chk.setChecked(self.device.simulate)
+ from core.app_settings import is_developer_mode
+ self.sim_chk.setVisible(is_developer_mode())
ni_form.addRow(self.sim_chk)
scan_row = QHBoxLayout()
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)