Question about get_position and set_position

3D printers typically get their positioning based on steps the motors make. Thus, if there are skipped steps by say some resistance to motion, the position information becomes slightly wrong.

I feel this is not how the uArm gets it positioning because get_position doesn’t return exactly the position given by set_position. The uArm gets it from some other independent sensors. Is this correct?

Yes, it get position by the encoder.

I don’t think this is correct. As I struggled a lot with this post and couldn’t believe that there is a mistake here, I want to share what I found:
I checked the Marlin code, and in fact get_position(), get_polar(), set_position() and set_polar() use the information of the stepper motors to calculate the position and the (cylindrical) polar coordinates, respectively. You have to call get_servo_angle() to get the encoder information. However, this returns the encoder readings as angles of the three encoders, rotation at the base, and the two angles of the arm. The conversion of these angles into x/y/z coordinates is a little bit complicated and done in Marlin internally. The function swift.angles_to_coordinates(angles) allows you to let Marlin convert them for you.
To verify this, you can run a script as the following and compare the outputs of:

swift.set_position(x, y, z)
print(x, y, z)
print(swift.get_position())
print(swift.angles_to_coordinates(swift.get_servo_angle()))

You will see that - as you said - it is true that x, y, z and the return value of get_position differ slightly. However, this is only due to rounding errors in the interpolation Marlin does in the background. Those values will not differ any more or less if you e.g. force the arm to skip a step by blocking its movement. The return value of the last line will actually read the encoder values and convert it to x/y/z coordinates, and this will also reflect any missed steps. As set_position and set_polar check the encoders before moving, they will still always aim at the right target, even if they missed steps before.

You can also call set_servo_detach(), which will turn the motors off, allowing you to move the arm freely. When you call get_position() or get_polar() now, they will return the values of the last position when the servos were still attached. This is again due to them calculating the position from the number of steps Marlin thinks the motors did. However, get_servo_angle() will still give you the correct position of the arm.

This is a very interesting post.

get_servo_angle() is the better function to call then if it gets it from the information from the encoders.

I was trying your last line of code to test out if you are right. There does not appear to be a swift.angles_to_coordinates() function in v1 or v2 of the sdk. swift.get_servo_angle() works fine though.

I see. You used the M2221 gcode for conversion and did some parsing of the returned results. Since you obviously wrote the function already, do share. :slight_smile:

My bad, the correct name is swift.angles_to_coordinate() without the s at the end. You are right, it is just a wrapper for M2221. I didn’t check the older API versions, I used API Version 3.2.0.

In case it doesn’t exist in older versions you can easily add it. It is a method of the swift class (in init.py) and looks like this:

@catch_exception
def angles_to_coordinate(self, angles=None, wait=True, timeout=None, callback=None):
    def _handle(_ret, _callback=None):
        if _ret[0] == protocol.OK:
            _ret = list(map(lambda i: float(i[1:]), _ret[1:]))
        elif _ret != protocol.TIMEOUT:
            _ret = _ret[0]
        if callable(_callback):
            _callback(_ret)
        else:
            return _ret

    assert isinstance(angles, (list, tuple)) and len(angles) >= 3

    cmd = protocol.ANGLES_TO_COORDINATE.format(*angles[:3])
    if wait:
        ret = self.send_cmd_sync(cmd, timeout=timeout)
        return _handle(ret)
    else:
        self.send_cmd_async(cmd, timeout=timeout, callback=functools.partial(_handle, _callback=callback))

only other thing you have to add should be the line
ANGLES_TO_COORDINATE = “M2221 B{} L{} R{}”
in protocol.py in order to define the g-code message

Thanks Fruchti, it seems the python sdk from github is a ‘newer’ version for v2 than mine with at least a few more additional wrapper functions.