from .filters import clamp from .paths import ensure_project_paths class GripperClient: def __init__(self, config): self.config = config self.motor = None def connect(self): if self.config.dry_run: return self ensure_project_paths() from changingtek_p_rtu_Servo import MotorController self.motor = MotorController( self.config.port, self.config.slave_id, baudrate=self.config.baudrate, timeout=self.config.serial_timeout, ) return self def move(self, position, force_pct, label, speed_pct=None, accel=None, decel=None): config = self.config speed_pct = config.speed if speed_pct is None else speed_pct accel = config.accel if accel is None else accel decel = config.decel if decel is None else decel if config.dry_run: print( f"[dry-run] {label}: temp_move " f"position={position}, speed={speed_pct}, force={force_pct}, " f"accel={accel}, decel={decel}" ) return self.motor.temp_move( position_mm=int(position), speed_pct=int(speed_pct), force_pct=int(force_pct), accel=int(accel), decel=int(decel), trigger=True, ) def open(self, force_pct, label="open", speed_pct=None, accel=None, decel=None): self.move( self.config.open_pos, force_pct, label, speed_pct=speed_pct, accel=accel, decel=decel, ) def close(self, force_pct, label="close"): self.move(self.config.close_pos, force_pct, label) def set_force(self, force_pct): config = self.config force_pct = int(clamp(force_pct, config.force_min, config.force_max)) if config.dry_run: print(f"[dry-run] set_target_force {force_pct}") return force_pct self.motor.set_target_force(force_pct) if config.trigger_force_update: self.motor.trigger_motion() return force_pct def read_position(self): if self.config.dry_run: return None try: return self.motor.read_real_position() except Exception as exc: print(f"read_real_position failed: {exc}") return None def hold_current_position(self, force_pct): current_pos = self.read_position() if current_pos is None: current_pos = self.config.hold_fallback_pos if current_pos is None: self.set_force(force_pct) self._stop_by_speed() return None self.move(int(current_pos), force_pct, "clamp-hold") return int(current_pos) def final_open_if_needed(self): if not self.config.open_at_end: return final_force = int(clamp( self.config.initial_force, self.config.force_min, self.config.force_max, )) self.open(final_force, "open-at-end") def _stop_by_speed(self): if self.config.dry_run: return try: self.motor.set_target_speed(0) self.motor.trigger_motion() except Exception as exc: print(f"stop by speed=0 failed: {exc}")