@@ -613,10 +613,15 @@ def move_stepper(self, steps=(0,0,0,0), speed=(1000,1000,1000,1000), is_absolute
613613 if isAbsoluteArray [iMotor ]:
614614 # Compare current position (physical) with target (physical, already includes offset)
615615 self .currentDirection [iMotor ] = 1 if (self .currentPosition [iMotor ] > targetPositionPhysical [iMotor ]) else - 1
616- # Calculate distance to travel in HARDWARE STEPS:
617- # Current position (physical) -> convert to steps, then subtract target (already in steps)
618- currentPosition_steps = self .currentPosition [iMotor ] / stepSizes [iMotor ]
619- absoluteDistances_steps [iMotor ] = abs (currentPosition_steps - steps [iMotor ])
616+ # Travel distance for the time estimate = |current - target| in
617+ # PHYSICAL units, converted to hardware steps. Computing it in
618+ # physical units keeps it direction-agnostic: the hardware `steps`
619+ # target carries the per-axis direction sign, so subtracting it
620+ # from an unsigned current-in-steps produced a *sum* (not a
621+ # difference) on inverted axes, hugely inflating the estimate.
622+ absoluteDistances_steps [iMotor ] = abs (
623+ self .currentPosition [iMotor ] - targetPositionPhysical [iMotor ]
624+ ) / stepSizes [iMotor ]
620625 else :
621626 self .currentDirection [iMotor ] = np .sign (steps [iMotor ])
622627 # For relative motion, steps[iMotor] is already the distance in hardware steps
@@ -629,19 +634,20 @@ def move_stepper(self, steps=(0,0,0,0), speed=(1000,1000,1000,1000), is_absolute
629634 if not isAbsoluteArray [iMotor ]:
630635 absoluteDistances_steps [iMotor ] = abs (steps [iMotor ])
631636
632- # Convert speed and acceleration from physical units to steps/second
637+ # Speed and acceleration are already in firmware step units (the same raw
638+ # values sent to the device), and absoluteDistances_steps is in hardware
639+ # steps too, so the time estimate is unit-consistent WITHOUT any stepSize
640+ # division — dividing here would desync it from the distance and break the
641+ # estimate. Just take magnitudes.
633642 speed_steps = np .zeros (4 )
634643 acceleration_steps = np .zeros (4 )
635644 for iMotor in range (4 ):
636645 if speed [iMotor ] != 0 :
637- # Speed: µm/s -> steps/s => divide by stepSize (µm/step)
638- speed_steps [iMotor ] = abs (speed [iMotor ]) # TODO: This is actually given in steps/s / stepSizes[iMotor]
646+ speed_steps [iMotor ] = abs (speed [iMotor ])
639647 if acceleration [iMotor ] is not None and acceleration [iMotor ] != 0 :
640- # Acceleration: µm/s² -> steps/s² => divide by stepSize
641- acceleration_steps [iMotor ] = abs (acceleration [iMotor ]) # TODO: This is actually given in steps/s / stepSizes[iMotor]
648+ acceleration_steps [iMotor ] = abs (acceleration [iMotor ])
642649 else :
643- # Default acceleration in steps/s²
644- acceleration_steps [iMotor ] = 20000 # This should also be converted, but we use a safe default
650+ acceleration_steps [iMotor ] = 20000 # safe default (firmware steps/s^2)
645651
646652 # Calculate travel time using HARDWARE STEPS and converted speed/acceleration
647653 # Find the axis that will take the longest (limits overall movement time)
@@ -696,8 +702,10 @@ def move_stepper(self, steps=(0,0,0,0), speed=(1000,1000,1000,1000), is_absolute
696702 "redu" : int (is_reduced )}
697703 if acceleration [iMotor ] is not None :
698704 motorProp ["accel" ] = int (acceleration [iMotor ])
705+ motorProp ["acceleration" ] = int (acceleration [iMotor ])
699706 else :
700707 motorProp ["accel" ] = self .DEFAULT_ACCELERATION
708+ motorProp ["acceleleration" ] = self .DEFAULT_ACCELERATION
701709 motorPropList .append (motorProp )
702710 if len (motorPropList )== 0 :
703711 return "{'return':-1}"
@@ -1243,6 +1251,44 @@ def set_tmc_parameters(self, axis=0, msteps=None, rms_current=None, stall_value=
12431251 r = self ._parent .post_json (path , payload , timeout = timeout )
12441252 return r
12451253
1254+ def get_tmc_parameters (self , axis = 0 , timeout = 1 ):
1255+ ''' Read the TMC parameters for a specific axis back from the device.
1256+
1257+ Sends {"task":"/tmc_get", "axis":<n>} and parses the response. Returns a
1258+ dict with msteps/rms_current/sgthrs/semin/semax/blank_time/toff, or None
1259+ if the firmware does not implement TMC readback (older firmwares only
1260+ accept /tmc_act). Callers should fall back to their last-applied values
1261+ in that case.
1262+ '''
1263+ if type (axis ) == str :
1264+ axis = self .xyztTo1230 (axis )
1265+ path = "/tmc_get"
1266+ payload = {"task" : path }
1267+ if axis is not None :
1268+ payload ["axis" ] = axis
1269+ try :
1270+ r = self ._parent .post_json (path , payload , timeout = timeout )
1271+ if isinstance (r , list ):
1272+ r = r [0 ] if r else {}
1273+ if not isinstance (r , dict ):
1274+ return None
1275+ # firmware may nest the values under "tmc" or return them flat
1276+ tmc = r .get ("tmc" , r )
1277+ if not isinstance (tmc , dict ):
1278+ return None
1279+ keys = ("msteps" , "rms_current" , "sgthrs" , "semin" , "semax" , "blank_time" , "toff" )
1280+ if not any (k in tmc for k in keys ):
1281+ # nothing TMC-shaped came back -> readback unsupported
1282+ return None
1283+ return {k : tmc [k ] for k in keys if k in tmc }
1284+ except Exception as e :
1285+ self ._parent .logger .debug (f"get_tmc_parameters failed: { e } " )
1286+ return None
1287+
1288+ # camelCase alias used by the ImSwitch ESP32StageManager
1289+ def getTMCSettings (self , axis = 0 , timeout = 1 ):
1290+ return self .get_tmc_parameters (axis = axis , timeout = timeout )
1291+
12461292 def set_hard_limits (self , axis = 1 , enabled = True , polarity = 0 , timeout = 1 ):
12471293 '''
12481294 Configure hard limits (emergency stop) for a motor axis.
0 commit comments