# Python ControllerClient 接口逆向记录 ## 背景 本记录用于确认旧 `PyControllerClient` 对 Python 暴露了哪些接口,尤其确认是否能通过 Python client 直接查询或设置旧服务端运行态 `JointLimits`。 复核对象: ```text ../flyshot-uaes-interface/lib/PyControllerClient.cpython-37m-x86_64-linux-gnu.so ../FlyingShot/FlyingShot/Lib/PyControllerClient.cpython-37m-x86_64-linux-gnu.so ../flyshot-uaes-interface/lib/libControllerClient.so ../FlyingShot/FlyingShot/Lib/libControllerClient.so ../FlyingShot/FlyingShot/Include/ControllerClient/ControllerClient.h ../FlyingShot/FlyingShot/Include/ControllerClient/Types.h ../flyshot-uaes-interface/UseControllerClient.py ../flyshot-uaes-interface/main.py ``` 两份 Python 扩展与两份底层 client 库哈希一致: ```text PyControllerClient.cpython-37m-x86_64-linux-gnu.so SHA256=648CC23CBC6DF83822B58AC4A10211EE1DF8029AD8933D31032187748DF7F4BC libControllerClient.so SHA256=6D6FD3F20F0791F1CF11EEE5B1D479E2DCB6A1A2C8AB00A1165575BAB4B62813 ``` 因此 `flyshot-uaes-interface/lib` 与 `FlyingShot/FlyingShot/Lib` 中的 Python client 可视为同一份接口。 ## 暴露的 Python 类型 `PyControllerClient` 暴露以下类型: | 类型 | 来源 | 说明 | | --- | --- | --- | | `ControllerClient` | `ControllerClient.h` | TCP JSON client,高层控制入口 | | `JointPositions` | `Types.h` | 关节位置容器,可用 6 维列表构造,也支持下标读写 | | `Pose` | `Types.h` | TCP/末端位姿容器,C++ 侧为 7 元数组 | | `JointLimits` | `Types.h` | 关节上下限、速度、加速度、jerk 容器 | | `IOType` | `Types.h` | IO 枚举 | `IOType` 的枚举值: ```text IOType.kIOTypeDI = 1 IOType.kIOTypeDO = 2 IOType.kIOTypeRI = 8 IOType.kIOTypeRO = 9 ``` ## ControllerClient 暴露方法 二进制字符串和 C++ 公开头文件交叉确认,Python client 暴露的方法为: | Python 方法 | 典型调用 | 返回形态 | 说明 | | --- | --- | --- | --- | | `ConnectServer` | `c.ConnectServer(server_ip="127.0.0.1", port=50001)` | `bool` | 连接旧 `50001/TCP+JSON` 服务端 | | `GetServerVersion` | `c.GetServerVersion()` | `str` | Python 包装层把 C++ out 参数折叠成返回值 | | `GetClientVersion` | `c.GetClientVersion()` | `str` | 获取 client 版本 | | `SetUpRobot` | `c.SetUpRobot("FANUC_LR_Mate_200iD")` | `bool` | 按机器人名称初始化服务端机器人模型 | | `SetUpRobotFromEnv` | `c.SetUpRobotFromEnv(env_file)` | `bool` | 从环境文件初始化 | | `IsSetUp` | `c.IsSetUp()` | `bool` | 判断服务端是否已经初始化机器人 | | `SetShowTCP` | `c.SetShowTCP(is_show=True, axis_length=0.1, axis_size=2)` | `bool` | 仿真显示 TCP 坐标系 | | `GetName` | `c.GetName()` | `str` | 获取机器人名称 | | `GetDoF` | `c.GetDoF()` | `int` | 获取自由度 | | `SetActiveController` | `c.SetActiveController(sim=True)` | `bool` | 切换仿真/真实控制器 | | `Connect` | `c.Connect("192.168.10.101")` | `bool` | 连接机器人控制器 | | `Disconnect` | `c.Disconnect()` | `bool` | 断开机器人控制器 | | `EnableRobot` | `c.EnableRobot()` / `c.EnableRobot(8)` | `bool` | 使能机器人,参数为 buffer size | | `DisableRobot` | `c.DisableRobot()` | `bool` | 下使能 | | `GetSpeedRatio` | `c.GetSpeedRatio()` | `float` | 获取执行速度倍率 | | `SetSpeedRatio` | `c.SetSpeedRatio(0.8)` | `bool` | 设置执行速度倍率 | | `GetTCP` | `res, tcp = c.GetTCP()` | `(bool, Pose)` | 获取 TCP | | `SetTCP` | `c.SetTCP(tcp)` | `bool` | 设置 TCP | | `GetIO` | `res, value = c.GetIO(port=1, io_type=IOType.kIOTypeDI)` | `(bool, bool)` | 读取 IO | | `SetIO` | `c.SetIO(port=1, value=True, io_type=IOType.kIOTypeDO)` | `bool` | 写 IO | | `StopMove` | `c.StopMove()` | `bool` | 停止运动 | | `GetJointPosition` | `res, joints = c.GetJointPosition()` | `(bool, JointPositions)` | 获取当前关节角 | | `GetPose` | `res, pose = c.GetPose()` | `(bool, Pose)` | 获取当前末端位姿 | | `GetNearestIK` | `res, ik = c.GetNearestIK(pose, joint_seed=joints)` | `(bool, JointPositions)` | 按 seed 求最近 IK | | `MoveJoint` | `c.MoveJoint(joint_positions)` | `bool` | 关节运动 | | `ExecuteTrajectory` | `c.ExecuteTrajectory(waypoints=[...], method="icsp", save_traj=True)` | `bool` | 执行普通轨迹 | | `UploadFlyShotTraj` | `c.UploadFlyShotTraj(name, waypoints, shot_flags, offset_values, addrs)` | `bool` | 上传飞拍轨迹 | | `DeleteFlyShotTraj` | `c.DeleteFlyShotTraj(name)` | `bool` | 删除飞拍轨迹 | | `ListFlyShotTraj` | `c.ListFlyShotTraj()` | `list[str]` | 列出已上传飞拍轨迹 | | `ExecuteFlyShotTraj` | `c.ExecuteFlyShotTraj(name, move_to_start=True, method="icsp", save_traj=True)` | `bool` | 执行飞拍轨迹 | | `SaveTrajInfo` | `c.SaveTrajInfo(name, method="icsp")` | `bool` | 保存规划结果到 `~/Rvbust/Data` | | `IsFlyShotTrajValid` | `valid, time = c.IsFlyShotTrajValid(name, method="icsp", save_traj=True)` | `(bool, float)` | 检查飞拍轨迹是否合法并返回规划时长 | ## 没有暴露的关键接口 本轮重点确认:Python client 暴露方法中没有看到: ```text GetJointLimits SetJointLimits _GetJointLimits _SetJointLimits ``` 虽然 `PyControllerClient` 绑定了 `JointLimits` 类型,并且 `libControllerClient.so` 中存在 `JointLimits` 的输出运算符符号,但公开 `ControllerClient` 方法表中没有任何接收或返回 `JointLimits` 的 client 入口。 这和旧服务端二进制不同。旧服务端 `ControllerServer.cpython-37m-x86_64-linux-gnu.so` 中能看到: ```text ControllerServer.ControllerServer._GetJointLimits ControllerServer.ControllerServer._SetJointLimits ControllerServer.ControllerServer._IsWaypointInJointLimits ControllerServer.ControllerServer._IsTrajInJointLimits ControllerServer.ControllerServer._IsTrajInJerkLimits ``` 因此当前判断是: ```text Python client 公开 API 不能直接抓 runtime JointLimits; runtime JointLimits 查询能力存在于旧服务端内部,而不是 PyControllerClient 公开接口中。 ``` ## UAES Python 服务实际使用的接口 `../flyshot-uaes-interface/main.py` 只使用了公开 client 方法: - `ConnectServer` - `SetUpRobot` - `IsSetUp` - `EnableRobot` - `DisableRobot` - `SetActiveController` - `Connect` - `GetName` - `GetServerVersion` - `GetDoF` - `GetSpeedRatio` - `SetTCP` - `GetTCP` - `SetIO` - `GetJointPosition` - `MoveJoint` - `ListFlyShotTraj` - `UploadFlyShotTraj` - `ExecuteFlyShotTraj` - `SetSpeedRatio` - `DeleteFlyShotTraj` - `GetPose` 其中 `/execute_flyshot/` 调用: ```text c.ExecuteFlyShotTraj(name=name, move_to_start=True, method="icsp", save_traj=True) ``` `/set_speedRatio/` 调用: ```text c.SetSpeedRatio(speed) ``` 没有看到 UAES 服务通过 Python client 设置或查询 `JointLimits`。 2026-04-30 追加 `50001/TCP+JSON` 抓包复核后,这个判断进一步收敛。`all-50001-plan.pcap` 中已经抓到两次真实规划/执行请求: ```json {"cmd":"SetSpeedRatio","ratio":0.5} {"cmd":"ExecuteFlyShotTraj","method":"icsp","move_to_start":true,"name":"UTTC_MS11_TEST01","save_traj":true,"use_cache":false,"wait":true} {"cmd":"SetSpeedRatio","ratio":1.0} {"cmd":"ExecuteFlyShotTraj","method":"icsp","move_to_start":true,"name":"UTTC_MS11_TEST01","save_traj":true,"use_cache":false,"wait":true} ``` 请求中仍没有 `JointLimits / acc_limit / jerk_limit / velocity / acceleration / jerk`。因此公开 Python client 与公开 50001 JSON 都没有把规划限制作为参数传给 `ExecuteFlyShotTraj`。 另外,`main.py` 的 `/execute_trajectory/` 中出现: ```text c.yrxm(waypoints=joint_positions, method='icsp', save_traj=True) ``` `yrxm` 不在 `PyControllerClient` 暴露方法表中,按上下文应是 `ExecuteTrajectory` 的笔误;这条不影响飞拍主路径 `/execute_flyshot/`。 ## 对当前时长差异调查的含义 如果要抓旧系统规划时使用的 effective `vel/acc/jerk`,优先级应调整为: 1. 在旧服务端进程内直接调用或插桩 `_GetJointLimits`。 2. 或者逆向 `50001/TCP+JSON` 的 hidden command envelope,再尝试发送 `GetJointLimits` / `_GetJointLimits`。 3. 不应指望现有 `PyControllerClient.ControllerClient` 直接提供 `GetJointLimits`。 如果短期内无法进入旧服务端内部,新系统不再继续等待这份不可见状态;设计上使用 replacement-only 的内部规划约束参数补齐,优先限制规划加速度,例如 `planning_acceleration_scale`。该参数必须标注为新系统校准值,不能写成旧 Python client 或旧 50001 JSON 的公开字段。 最小现场验证脚本可以先确认 Python client 暴露面: ```python from PyControllerClient import ControllerClient c = ControllerClient() names = [x for x in dir(c) if "Limit" in x or "limit" in x] print(names) ``` 按当前二进制逆向,预期不会出现 `GetJointLimits` / `SetJointLimits`。