坐标系偏移

1. 简介

CS在进行一些二次开发时需要用到正逆解运算,以及坐标系偏移。本文借助代码介绍对应脚本的使用方法。

2. 相关指令

2.1 获取当前关节角度

get_actual_joint_positions()
功能:
该指令用于获取所有关节的实际关节角位置。实际关节角位置以弧度为单位,并以长度为 6 的矢量返回。
参数:
返回值:
list 型数据:[Base,Shoulder,Elbow,Wrist1,Wrist2,Wrist3],以弧度为单位的当前实际关节角位置。
示例:
j=get_actual_joint_positions()

2.2 获取当前TCP位姿

get_actual_tcp_pose()
功能:
该指令用于获取当前 TCP 位姿。返回 6D 位姿——6D 位姿表示基架中规定的工具位置和方位。此姿态的计算基于实际机器人编码器读数。
参数:
返回值:
当前实际 TCP 的位姿,格式为 [x,y,z,Rx,Ry,Rz],list 型数据,其中 x、y、z 单位为 m,Rx、Ry、Rz 单位为 rad。
示例:
b=get_actual_tcp_pose()

2.3 逆解计算

get_inverse_kin(p, qnear='', tcp='active tcp')
功能:
该指令用于运动学逆解计算,即从笛卡尔空间到关节空间的转换。如果定义了qnear,则返回最接近 qnear 的解。否则,将返回最接近当前关节位置的解。如果未提供 tcp,则将使用控制器的当前激活 tcp。
参数:
p:位姿,格式为 [x,y,z,Rx,Ry,Rz],list 型数据,其中 x、y、z 单位为 m,Rx、Ry、Rz 单位为 rad;
qnear:最接近的关节角度,缺省值为当前机器人关节角,list 型数据(可选参数);
tcp:工具形状(可选参数)。
返回值:
list 型数据,关节角度,单位:弧度。
示例:
po=[0.4,‑0.2,0.4,‑3.1,2.8,‑1.8]
qn=[‑0.2,‑1.6,‑1.4,‑1.5,1.5,‑1.6]
v=get_inverse_kin(po,qnear=qn)

2.4 正解计算

get_forward_kin(q='', tcp='active tcp')
功能:
该指令用于正运动学计算,即通过关节角度获取位姿,使用当前系统激活的 TCP数据。如果未提供关节位置矢量,则将使用机器人本体的当前关节角度。如果未提供 tcp,则将使用控制器的当前激活 tcp。
参数:
q:机器人的关节角度,缺省值为当前机器人关节角度,单位为弧度,list 型数据(可选参数)。
tcp:工具形状(可选参数)。
返回值:
机器人位姿,格式为 [x,y,z,Rx,Ry,Rz],list 型数据,其中 x、y、z 单位为 m,Rx、Ry、Rz 单位为 rad。
示例:
p_j=[‑0.2,‑1.6,‑1.4,‑1.5,1.5,0]
p_p=get_forward_kin(p_j)

2.5 位姿变换

pose_trans(p_from, p_from_to)
功能:
该指令用于姿态变换。第一个自变量 p_from 用于变换第二个自变量 p_from_to,然后返回结果。这意味着结果是从 p_from 坐标系开始,然后坐标系移动 p_from_to 生成的姿态。如果姿态被视为变换矩阵,则如下所示:
T_world->to=T_world->from * T_from->to
T_x->to=T_x->from * T_from->to
参数:
p_from:起始姿态,格式为 [x,y,z,Rx,Ry,Rz],list 型数据,其中 x、y、z 单位为 m,Rx、Ry、Rz 单位为 rad;
p_from_to:相对于起始姿态的姿态变化,格式为 [x,y,z,Rx,Ry,Rz],list 型数据,其中 x、y、z 单位为 m,Rx、Ry、Rz 单位为 rad。
返回值:
生成的姿态,格式为 [x,y,z,Rx,Ry,Rz],list 型数据,其中 x、y、z 单位为 m,Rx、Ry、Rz 单位为 rad。
示例:
pf=[0.4,‑0.2,0.4,‑3.1,2.8,‑2.3]
pft=[‑0.01,‑1.5,‑0.002,‑1.5,1.5,‑2.3]
tp=pose_trans(pf,pft)

2.6 位姿求逆

pose_inv(p_from)
功能:
该指令用于计算并返回位姿的逆。
参数:
p_from:工具位姿,格式为 [x,y,z,Rx,Ry,Rz],list 型数据,其中 x、y、z 单位为 m,Rx、Ry、Rz 单位为 rad。
返回值:
p_from 的逆,格式为 [x,y,z,Rx,Ry,Rz],list 型数据,其中 x、y、z 单位为 m,Rx、Ry、Rz 单位为 rad。
示例:
pf=[0.4,‑0.2,0.4,‑3.1,2.8,‑2.3]
inv_pose=pose_inv(pf)

3. 代码示例

#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
# Copyright (c) 2025, Elite Robots.
#
# 使用 40011 明文请求端口实现运动学示例
# 等价于 SDK 版本的 kinematics_example,无需任何 SDK 依赖
#
# 功能:
#   1. 获取当前关节角度 (get_actual_joint_positions)
#   2. 获取当前 TCP 位姿 (get_actual_tcp_pose)
#   3. 正解 FK: get_forward_kin(q)  → TCP 位姿
#   4. 逆解 IK: get_inverse_kin(p)  → 关节角度
#
# 用法:
#   python kinematics_via_port.py --robot-ip 192.168.1.200

import argparse
import ast
import socket
import sys


# ─────────────────────────────────────────────
# 40011 端口通讯工具
# ─────────────────────────────────────────────

def send_request(ip: str, req_id: int, cmd: str, timeout: float = 5.0):
    """
    向 40011 端口发送一条请求,返回 (result_list, error_str)。

    格式:req {id}{cmd}\n   ← id 与 cmd 之间无空格!
    成功响应:{id} success: {value}
    失败响应:{id} failure: {reason}
    """
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.settimeout(timeout)
    try:
        sock.connect((ip, 40011))
        payload = f"req {req_id}{cmd}\n"
        sock.sendall(payload.encode("utf-8"))
        resp = b""
        while True:
            chunk = sock.recv(4096)
            if not chunk:
                break
            resp += chunk
            if b"\n" in resp:
                break
    finally:
        sock.close()

    resp_str = resp.decode("utf-8", errors="replace").strip()

    if "failure" in resp_str.lower():
        return None, resp_str

    # 按第一个 ':' 分割
    parts = resp_str.split(":", 1)
    if len(parts) < 2:
        return None, f"unexpected response: {resp_str}"

    val_str = parts[1].strip()

    # 尝试解析为列表(可能是双重嵌套 [[...]])
    try:
        val = ast.literal_eval(val_str)
        if isinstance(val, list):
            # 展开双重嵌套
            if len(val) == 1 and isinstance(val[0], list):
                val = val[0]
            return [float(x) for x in val], None
        return float(val), None
    except Exception:
        pass

    # 降级:手动去括号解析
    try:
        clean = val_str.replace("[", "").replace("]", "").replace(" ", "")
        return [float(x) for x in clean.split(",") if x], None
    except Exception as e:
        return None, f"parse error: {e}, raw: {resp_str}"


# ─────────────────────────────────────────────
# 位姿工具函数
# ─────────────────────────────────────────────

def pose_inv(ip: str, req_id_ref: list, p: list) -> tuple:
    """
    调用机器人 pose_inv(p),返回 (result, next_req_id, error)。
    req_id_ref 是一个单元素列表,用于在函数内自增 ID。
    """
    p_native = [float(x) for x in p]
    result, err = send_request(ip, req_id_ref[0], f"pose_inv({p_native})")
    req_id_ref[0] += 1
    return result, err


def pose_trans(ip: str, req_id_ref: list, p1: list, p2: list) -> tuple:
    """
    调用机器人 pose_trans(p1, p2),返回 (result, error)。
    """
    p1_native = [float(x) for x in p1]
    p2_native = [float(x) for x in p2]
    result, err = send_request(ip, req_id_ref[0], f"pose_trans({p1_native},{p2_native})")
    req_id_ref[0] += 1
    return result, err


def offs(ip: str, req_id_ref: list, p: list, x: float, y: float, z: float,
         user: list = None) -> tuple:
    """
    等价于 EliteScript 的 Offs 函数:

        def Offs(p, x, y, z, user=[0,0,0,0,0,0]):
            out = p.copy()
            out_in_user = pose_trans(pose_inv(user), out)
            out_in_user[0] += x
            out_in_user[1] += y
            out_in_user[2] += z
            out = pose_trans(user, out_in_user)
            return out

    参数:
        ip          机器人 IP
        req_id_ref  [int],请求 ID 容器(自动递增)
        p           目标位姿 [x,y,z,Rx,Ry,Rz],基坐标系,单位 m/rad
        x, y, z     在用户坐标系下的偏移量,单位 m
        user        用户坐标系位姿,默认 [0,0,0,0,0,0](即基坐标系)

    返回:(偏移后位姿, error_str)
    """
    if user is None:
        user = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0]

    # Step 1: pose_inv(user)
    inv_user, err = pose_inv(ip, req_id_ref, user)
    if err:
        return None, f"pose_inv failed: {err}"

    # Step 2: out_in_user = pose_trans(inv_user, p)
    out_in_user, err = pose_trans(ip, req_id_ref, inv_user, p)
    if err:
        return None, f"pose_trans(inv_user, p) failed: {err}"

    # Step 3: 在用户坐标系下施加偏移
    out_in_user[0] += float(x)
    out_in_user[1] += float(y)
    out_in_user[2] += float(z)

    # Step 4: out = pose_trans(user, out_in_user)
    out, err = pose_trans(ip, req_id_ref, user, out_in_user)
    if err:
        return None, f"pose_trans(user, out_in_user) failed: {err}"

    return out, None


def log_vector(prefix: str, vec):
    vals = ", ".join(f"{v:.6f}" for v in vec)
    print(f"{prefix} [{vals}]")


# ─────────────────────────────────────────────
# 主程序
# ─────────────────────────────────────────────

def main():
    ip = "172.16.15.45"
    req_id = 1  # 请求 ID,每次递增

    print(f"[INFO] 连接机器人: {ip}:40011")

    # ── Step 1: 获取当前关节角度 ──────────────────
    joints, err = send_request(ip, req_id, "get_actual_joint_positions()")
    req_id += 1
    if err:
        print(f"[FATAL] 获取关节角度失败: {err}")
        sys.exit(1)
    print("[INFO] 获取实际关节角度成功")
    log_vector("Current Joints:", joints)

    # ── Step 2: 获取当前 TCP 位姿 ─────────────────
    tcp, err = send_request(ip, req_id, "get_actual_tcp_pose()")
    req_id += 1
    if err:
        print(f"[FATAL] 获取 TCP 位姿失败: {err}")
        sys.exit(1)
    print("[INFO] 获取实际 TCP 位姿成功")
    log_vector("Current TCP Pose:", tcp)

    # ── Step 3: 正解 FK ───────────────────────────
    # get_forward_kin(q) → TCP 位姿
    # 参数必须是 Python 原生 float 列表
    joints_native = [float(x) for x in joints]
    fk_cmd = f"get_forward_kin({joints_native})"
    print(fk_cmd)
    fk_pose, err = send_request(ip, req_id, fk_cmd)
    req_id += 1
    if err:
        print(f"[FATAL] 正解 FK 失败: {err}")
        sys.exit(1)
    print("[INFO] 正解 FK 成功")
    log_vector("FK Pose:", fk_pose)

    # ── Step 4: 逆解 IK ───────────────────────────
    # get_inverse_kin(p, qnear='', tcp='active tcp')
    # 以当前 TCP 位姿为目标,当前关节角为参考解
    tcp_native = [float(x) for x in tcp]
    joints_ref = [float(x) for x in joints]
    ik_cmd = f"get_inverse_kin({tcp_native}, {joints_ref})"
    print(ik_cmd)
    ik_joints, err = send_request(ip, req_id, ik_cmd)
    req_id += 1
    if err:
        print(f"[FATAL] 逆解 IK 失败: {err}")
        sys.exit(1)
    print("[INFO] 逆解 IK 成功")
    log_vector("IK Result Joints:", ik_joints)

    # ── 汇总输出(与 SDK 版本格式一致)──────────
    log_vector("Current TCP Pose:", tcp)
    log_vector("FK Pose:", fk_pose)
    log_vector("IK Result Joints:", ik_joints)
    log_vector("Current Joints:", joints)

    # ── Step 5: Offs 演示 ─────────────────────────────────
    # 在当前 TCP 位姿基础上,在基坐标系 X 方向偏移 +50mm
    print("[INFO] 测试 Offs: 在基坐标系 X 方向偏移 +50mm")
    req_id_ref = [req_id]
    tcp_native = [float(v) for v in tcp]
    offs_result, err = offs(ip, req_id_ref, tcp_native, x=0.05, y=0.0, z=0.0)
    req_id = req_id_ref[0]
    if err:
        print(f"[ERROR] Offs 失败: {err}")
    else:
        log_vector("Offs Result (X+50mm):", offs_result)


if __name__ == "__main__":
    main()
输出将如下所示:
FK Pose: [-0.203082, -0.255123, 0.284660, 2.043829, 0.427645, 1.090979]
IK Result Joints: [0.200621, -1.609931, 1.694720, -0.711048, -0.785667, -0.000485]
Current Joints: [0.200621, -1.609931, 1.694719, -0.711047, -0.785668, -0.000485]
[INFO] 测试 Offs: 在基坐标系 X 方向偏移 +50mm
Offs Result (X+50mm): [-0.153082, -0.255123, 0.284660, 2.043828, 0.427645, 1.090978]