From 2014b045fc62567aeb06ea610a98aa03a43b7142 Mon Sep 17 00:00:00 2001 From: Christian Kolset Date: Mon, 17 Aug 2026 10:02:28 -0600 Subject: Fix control panel freeze and CM1 motor not moving MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ControlWidget._write() called dev.write_channel() directly on the GUI thread from Qt signal handlers — for CML (CoolMuscle), write() does 2-3 blocking serial round-trips, and a slider fires valueChanged on every drag tick, so each tick froze the whole app until the device responded or timed out. Replaced with a coalescing background dispatch: writes are keyed by (device, channel) in a dict instead of a plain FIFO queue, so re-writing the same channel while a write is still pending updates its value in place rather than piling up — a strict FIFO was observed backing up 30+ deep during a single drag, meaning the motor kept "moving" to seconds-old positions long after the slider stopped, which looked like a freeze even once off the GUI thread. Separately, the motor never actually moved because it was never enabled: per docs/CM1-C_ASCII_Command_Cheatsheet.md, a CM1-C motor stays disabled ("shaft free") until sent "(", and nothing in CMLLayer.write()'s VS/MA handling ever sent it — enable only existed as a separate M1_ME channel the generic MotorControl widget has no concept of. VS/MA now send enable unconditionally before the move sequence (a no-op if already enabled). Co-Authored-By: Claude Sonnet 5 --- api_layers/protocols/cml.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'api_layers/protocols') diff --git a/api_layers/protocols/cml.py b/api_layers/protocols/cml.py index 113afe7..e085adf 100644 --- a/api_layers/protocols/cml.py +++ b/api_layers/protocols/cml.py @@ -149,6 +149,16 @@ class CMLLayer(BaseProtocol): self._send(motor.address, "]") self._read_reply() elif cmd == "VS": + # Motor must be enabled ("(") or the shaft stays free and + # every move command below is silently accepted but + # produces no motion — see docs/CM1-C_ASCII_Command_ + # Cheatsheet.md "Safety and enable commands". Enabling + # an already-enabled motor is a no-op, so it's safe to + # send unconditionally on every move rather than relying + # on the operator to have pressed a separate M1_ME + # control first. + self._send(motor.address, "(") + self._read_reply() self._send(motor.address, "P0", _CONTINUOUS_POSITION) self._read_reply() self._send(motor.address, "S0", int(value)) @@ -156,6 +166,8 @@ class CMLLayer(BaseProtocol): self._send(motor.address, "^") self._read_reply() elif cmd == "MA": + self._send(motor.address, "(") + self._read_reply() self._send(motor.address, "P0", int(value)) self._read_reply() self._send(motor.address, "^") -- cgit v1.2.3