carm 0.0.20250822__py3-none-any.whl → 0.0.20250911__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
carm/carm.py CHANGED
@@ -10,20 +10,20 @@ class Carm:
10
10
  self.last_msg = None
11
11
  self.ws = websocket.WebSocketApp(
12
12
  addr, # 测试用的公开WebSocket服务
13
- on_open = lambda ws: self.on_open(ws),
14
- on_close = lambda ws, code, close_msg: self.on_close(ws, code, close_msg),
15
- on_message= lambda ws, msg: self.on_message(ws, msg),
13
+ on_open = lambda ws: self.__on_open(ws),
14
+ on_close = lambda ws, code, close_msg: self.__on_close(ws, code, close_msg),
15
+ on_message= lambda ws, msg: self.__on_message(ws, msg),
16
16
  )
17
17
 
18
18
  self.ops = {
19
- "webSendRobotState": lambda msg: self.cbk_status(msg),
20
- "taskFinished": lambda msg: self.cbk_taskfinish(msg),
19
+ "webSendRobotState": lambda msg: self.__cbk_status(msg),
20
+ "taskFinished": lambda msg: self.__cbk_taskfinish(msg),
21
21
  "onCarmError": lambda msg: print("Error:", msg)
22
22
  }
23
23
  self.res_pool = {}
24
24
  self.task_pool = {}
25
25
 
26
- self.reader = threading.Thread(target=self.recv_loop, daemon=True).start()
26
+ self.reader = threading.Thread(target=self.__recv_loop, daemon=True).start()
27
27
  self.open_ready = threading.Event()
28
28
  self.open_ready.wait()
29
29
  self.limit = self.get_limits()["params"]
@@ -32,12 +32,22 @@ class Carm:
32
32
 
33
33
  @property
34
34
  def version(self):
35
+ """
36
+ Get the version of the arm.
37
+ Returns:
38
+ dict: The version of the arm.
39
+ """
35
40
  return self.request({"command":"getArmIntrinsicProperties",
36
41
  "arm_index":0,
37
42
  "type":"version"})
38
43
 
39
44
 
40
45
  def get_limits(self):
46
+ """
47
+ 获取主要配置参数,包括关节限位、关节最大速度、加速度、加加速度等。
48
+ Returns:
49
+ dict: The limits of the arm.
50
+ """
41
51
  return self.request({"command":"getJointParams",
42
52
  "arm_index":0,
43
53
  "type":"version"})
@@ -58,16 +68,42 @@ class Carm:
58
68
  return self.set_control_mode(1)
59
69
 
60
70
  def set_servo_enable(self, enable=True):
71
+ """
72
+ Set the servo enable of the arm.
73
+ Args:
74
+ enable (bool): The servo enable to set.
75
+ Returns:
76
+ dict: The response from the arm.
77
+ """
61
78
  return self.request({"command":"setServoEnable",
62
79
  "arm_index":0,
63
80
  "enable":enable})
64
81
 
65
82
  def set_control_mode(self, mode=1):
83
+ """
84
+ Set the control mode of the arm.
85
+ Args:
86
+ mode (int): The control mode to set.
87
+ * 0-IDLE空闲模式
88
+ * 1-点位控制模式
89
+ * 2-MIT控制模式
90
+ * 3-关节拖动模式
91
+ Returns:
92
+ dict: The response from the arm.
93
+ """
66
94
  return self.request({"command":"setControlMode",
67
95
  "arm_index":0,
68
96
  "mode":mode})
69
97
 
70
98
  def set_end_effector(self, pos, tau):
99
+ """
100
+ Set the end effector of the arm.
101
+ Args:
102
+ pos (float): The position of the end effector. 0-0.08m
103
+ tau (float): The torque of the end effector. 0-9.0N.m
104
+ Returns:
105
+ dict: The response from the arm.
106
+ """
71
107
  pos = self.__clip(pos, 0, 0.08)
72
108
  tau = self.__clip(tau, 0, 9)
73
109
 
@@ -77,6 +113,13 @@ class Carm:
77
113
  "tau": tau})
78
114
 
79
115
  def get_tool_coordinate(self, tool):
116
+ """
117
+ 获取指定工具的坐标系(工具末端相对法兰的位姿关系).
118
+ Args:
119
+ tool (int): The tool index.
120
+ Returns:
121
+ dict: The tool coordinate of the arm.
122
+ """
80
123
  return self.request({"command":"getCoordinate",
81
124
  "arm_index":0,
82
125
  "type": "tool",
@@ -84,12 +127,31 @@ class Carm:
84
127
 
85
128
 
86
129
  def set_collision_config(self, flag = True, level = 10):
130
+ """
131
+ 设置碰撞检测配置.
132
+ Args:
133
+ flag (bool): 是否开启碰撞检测.
134
+ level (int): 碰撞检测等级. 灵敏度等级 0-2,0最高,2最低
135
+ Returns:
136
+ dict: The response from the arm.
137
+ """
87
138
  return self.request({"command":"setCollisionConfig",
88
139
  "arm_index":0,
89
140
  "flag": flag,
90
141
  "level": level})
91
142
 
92
143
  def stop(self, type=0):
144
+ """
145
+ 停止carm.
146
+ Args:
147
+ type (int): 停止类型.
148
+ * 0-暂停
149
+ * 1-停止
150
+ * 2-禁用
151
+ * 3-紧急停止
152
+ Returns:
153
+ dict: The response from the arm.
154
+ """
93
155
  stop_id = ["SIG_ARM_PAUSE", "SIG_ARM_STOP",
94
156
  "SIG_ARM_DISABLE","SIG_EMERGENCY_STOP"]
95
157
  return self.request({"command":"stopSignals",
@@ -98,57 +160,119 @@ class Carm:
98
160
  "step_cnt":5})
99
161
 
100
162
  def stop_task(self, at_once=False):
163
+ """
164
+ 停止当前任务.
165
+ Args:
166
+ at_once (bool): 是否立即停止任务.
167
+ Returns:
168
+ dict: The response from the arm.
169
+ """
101
170
  return self.request({"command":"stopSignals",
102
171
  "arm_index":0,
103
172
  "stop_id": "SIG_TASK_STOP",
104
173
  "stop_at_once": at_once})
105
174
 
106
175
  def recover(self):
176
+ """
177
+ 恢复carm.
178
+ Returns:
179
+ dict: The response from the arm.
180
+ """
107
181
  return self.request({"command":"stopSignals",
108
182
  "arm_index":0,
109
183
  "stop_id": "SIG_ARM_RECOVER",
110
184
  "step_cnt": 5})
111
185
 
112
186
  def clean_carm_error(self):
187
+ """
188
+ 清除carm错误.
189
+ Returns:
190
+ dict: The response from the arm.
191
+ """
113
192
  return self.request({"command":"setControllerErrorReset",
114
193
  "arm_index":0})
115
194
 
116
- def set_speed_level(self, level, response_level):
195
+ def set_speed_level(self, level = 5.0, response_level = 20):
196
+ """
197
+ Set the speed level of the arm.
198
+ Args:
199
+ level (float): The speed level to set. range from 0 to 10. 0 is the slowest, 10 is the fastest.
200
+ response_level (int): The response level to set, after how many cicles to set. From 10 to 100.
201
+ Returns:
202
+ dict: The response from the arm.
203
+ """
117
204
  return self.request({"command":"setSpeedLevel",
118
205
  "arm_index":0,
119
206
  "level":level,
120
207
  "response_level":response_level})
121
-
208
+
122
209
  def set_debug(self, flag):
210
+ """
211
+ 设置调试模式.
212
+ Args:
213
+ flag (bool): 是否开启调试模式.
214
+ Returns:
215
+ dict: The response from the arm.
216
+ """
123
217
  return self.request({"command":"setDebugMode",
124
218
  "arm_index":0,
125
219
  "trigger":flag})
126
220
 
127
221
  def get_urdf(self):
222
+ """
223
+ 获取urdf模型.
224
+ Returns:
225
+ dict: The urdf model of the arm.
226
+ """
128
227
  return self.request({"command":"getArmIntrinsicProperties",
129
228
  "arm_index":0,
130
229
  "type":"urdf"})
131
230
  @property
132
231
  def joint_pos(self):
232
+ """
233
+ 获取当前关节位置.
234
+ Returns:
235
+ list: The joint position of the arm.
236
+ """
133
237
  return self.state["arm"][0]["reality"]["pose"]
134
238
 
135
239
  @property
136
240
  def joint_vel(self):
241
+ """
242
+ 获取当前关节速度.
243
+ Returns:
244
+ list: The joint velocity of the arm.
245
+ """
137
246
  return self.state["arm"][0]["reality"]["vel"]
138
247
 
139
248
  @property
140
249
  def joint_tau(self):
250
+ """
251
+ 获取当前关节力矩.
252
+ Returns:
253
+ list: The joint torque of the arm.
254
+ """
141
255
  return self.state["arm"][0]["reality"]["torque"]
142
256
 
143
257
  @property
144
258
  def cart_pose(self):
259
+ """
260
+ 获取当前笛卡尔位置.
261
+ Returns:
262
+ list: The cartesian position of the arm.
263
+ """
145
264
  return self.state["arm"][0]["pose"]
146
265
 
147
266
  @property
148
267
  def gripper_state(self):
268
+ """
269
+ 获取当前夹爪状态.
270
+ Returns:
271
+ dict: The gripper state of the arm.
272
+ """
149
273
  return self.state["arm"][0]["gripper"]
150
274
 
151
- def clip_joints(self, joints):
275
+ def __clip_joints(self, joints):
152
276
  lower = self.limit['limit_lower']
153
277
  upper = self.limit["limit_upper"]
154
278
  for i,v in enumerate(joints):
@@ -156,7 +280,15 @@ class Carm:
156
280
  return joints
157
281
 
158
282
  def track_joint(self, pos, end_effector = -1):
159
- pos = self.clip_joints(pos)
283
+ """
284
+ 关节轨迹跟踪.
285
+ Args:
286
+ pos (list): The joint position to track.
287
+ end_effector (float, optional): The end effector width. -1 means not set.
288
+ Returns:
289
+ dict: The response from the arm.
290
+ """
291
+ pos = self.__clip_joints(pos)
160
292
  req = {"command":"trajectoryTrackingTasks",
161
293
  "task_id":"TASK_TRACKING",
162
294
  "arm_index":0,
@@ -169,6 +301,14 @@ class Carm:
169
301
  return self.request(req)
170
302
 
171
303
  def track_pose(self, pos, end_effector = -1):
304
+ """
305
+ 笛卡尔轨迹跟踪.
306
+ Args:
307
+ pos (list): The cartesian position to track.
308
+ end_effector (float, optional): The end effector width. -1 means not set.
309
+ Returns:
310
+ dict: The response from the arm.
311
+ """
172
312
  req = {"command":"trajectoryTrackingTasks",
173
313
  "task_id":"TASK_TRACKING",
174
314
  "arm_index":0,
@@ -181,7 +321,18 @@ class Carm:
181
321
  return self.request(req)
182
322
 
183
323
  def move_joint(self, pos, tm=-1, sync=True, user=0, tool=0):
184
- pos = self.clip_joints(pos)
324
+ """
325
+ 关节到点运动
326
+ Args:
327
+ pos (list): The joint position to move.
328
+ tm (float, optional): The time to move. -1 means not set.
329
+ sync (bool, optional): Whether to wait for the movement to finish.
330
+ user (int, optional): The user index.
331
+ tool (int, optional): The tool index.
332
+ Returns:
333
+ dict: The response from the arm.
334
+ """
335
+ pos = self.__clip_joints(pos)
185
336
 
186
337
  res = self.request({"command":"webRecieveTasks",
187
338
  "task_id":"TASK_MOVJ",
@@ -191,11 +342,22 @@ class Carm:
191
342
  "data":{"user":user,"tool":tool, "target_pos": pos, "speed":100}})
192
343
 
193
344
  if sync and res["recv"]=="Task_Recieve":
194
- self.wait_task(res["task_key"])
345
+ self.__wait_task(res["task_key"])
195
346
 
196
347
  return res
197
348
 
198
349
  def move_pose(self, pos, tm=-1, sync=True, user=0, tool=0):
350
+ """
351
+ 笛卡尔到点运动
352
+ Args:
353
+ pos (list): The cartesian position to move.
354
+ tm (float, optional): The time to move. -1 means not set.
355
+ sync (bool, optional): Whether to wait for the movement to finish.
356
+ user (int, optional): The user index.
357
+ tool (int, optional): The tool index.
358
+ Returns:
359
+ dict: The response from the arm.
360
+ """
199
361
  res = self.request({"command":"webRecieveTasks",
200
362
  "task_id":"TASK_MOVJ",
201
363
  "task_level":"Task_General",
@@ -204,25 +366,42 @@ class Carm:
204
366
  "data":{"user":user,"tool":tool, "target_pos": pos, "speed":100}})
205
367
 
206
368
  if sync and res["recv"]=="Task_Recieve":
207
- self.wait_task(res["task_key"])
369
+ self.__wait_task(res["task_key"])
208
370
 
209
371
  return res
210
372
 
211
373
 
212
374
  def move_line(self, pos, speed=50, sync=True, user=0, tool=0):
375
+ """
376
+ 笛卡尔直线运动
377
+ Args:
378
+ pos (list): The cartesian position to move.
379
+ speed (float, optional): The speed to move. 0 - 100%.
380
+ sync (bool, optional): Whether to wait for the movement to finish.
381
+ user (int, optional): The user index.
382
+ tool (int, optional): The tool index.
383
+ Returns:
384
+ dict: The response from the arm.
385
+ """
386
+ ret = self.invK(pos, self.joint_pos, user, tool) # check IK first
387
+ if ret["recv"] != "Task_Recieve":
388
+ print("Inverse kinematics failed:", ret)
389
+ return ret
390
+
391
+ pos = ret["data"]["joint1"]
213
392
  res = self.request({"command":"webRecieveTasks",
214
393
  "task_id":"TASK_MOVL",
215
394
  "task_level":"Task_General",
216
395
  "arm_index":0,
217
- "point_type":{"space":1},
218
- "data":{"user":user,"tool":tool, "target_pos": pos, "speed":speed}})
396
+ "point_type":{"space":0},
397
+ "data":{"user":user,"tool":tool, "point": pos, "speed":speed}})
219
398
 
220
399
  if sync and res["recv"]=="Task_Recieve":
221
- self.wait_task(res["task_key"])
400
+ self.__wait_task(res["task_key"])
222
401
 
223
402
  return res
224
403
 
225
- def wait_task(self, task_key):
404
+ def __wait_task(self, task_key):
226
405
  event = threading.Event()
227
406
  self.task_pool[task_key] = {"event":event}
228
407
  event.wait()
@@ -247,13 +426,28 @@ class Carm:
247
426
  pass # TODO
248
427
 
249
428
  def invK(self, cart_pose, ref_joints, user=0, tool=0):
429
+ """
430
+ 逆解
431
+ Args:
432
+ cart_pose (list): The cartesian position to move.
433
+ ref_joints (list): The reference joint position.
434
+ user (int, optional): The user index.
435
+ tool (int, optional): The tool index.
436
+ Returns:
437
+ dict: The response from the arm.
438
+ """
250
439
  if not type(cart_pose[0]) is list:
251
440
  cart_pose = [cart_pose, ]
441
+ ref_joints = [ref_joints, ]
252
442
  assert(len(cart_pose) == len(ref_joints))
443
+ data = {"user":user,"tool":tool,"point_cnt":len(cart_pose)}
444
+ for i in range(len(ref_joints)):
445
+ data[f"point{i+1}"] = cart_pose[i]
446
+ data[f"refer{i+1}"] = ref_joints[i]
253
447
  return self.request({"command":"getKinematics",
254
448
  "task_id":"inverse",
255
449
  "arm_index":0,
256
- "data":{"user":user,"tool":tool,"point_cnt":len(cart_pose)}})
450
+ "data":data})
257
451
 
258
452
  def request(self, req):
259
453
  event = threading.Event()
@@ -261,43 +455,43 @@ class Carm:
261
455
  req["task_key"] = task_key
262
456
  self.res_pool[task_key] = {"req":req,"event":event}
263
457
 
264
- self.send(req)
458
+ self.__send(req)
265
459
 
266
460
  event.wait()
267
461
  return self.res_pool.pop(task_key)["res"]
268
462
 
269
- def send(self, msg):
463
+ def __send(self, msg):
270
464
  self.ws.send(json.dumps(msg))
271
465
 
272
- def cbk_status(self, message):
466
+ def __cbk_status(self, message):
273
467
  self.state = message
274
468
 
275
- def cbk_taskfinish(self, message):
469
+ def __cbk_taskfinish(self, message):
276
470
  task = message["task_key"]
277
471
  self.task_pool[task]["event"].set()
278
472
 
279
- def on_open(self, ws):
473
+ def __on_open(self, ws):
280
474
  self.open_ready.set()
281
475
  print("Connected successfully.")
282
476
 
283
- def on_close(self, ws, code, close_msg):
477
+ def __on_close(self, ws, code, close_msg):
284
478
  print("Disconnected, please check your --addr",code, close_msg)
285
479
 
286
- def on_message(self, ws, message):
480
+ def __on_message(self, ws, message):
287
481
  msg = json.loads(message)
288
482
  self.last_msg = msg
289
483
  cmd = msg["command"]
290
- op = self.ops.get(cmd, lambda msg: self.response_op(msg))
484
+ op = self.ops.get(cmd, lambda msg: self.__response_op(msg))
291
485
  op(msg)
292
486
 
293
- def response_op(self, res):
487
+ def __response_op(self, res):
294
488
  id = res.get("task_key","")
295
489
  data = self.res_pool[id]
296
490
  data["res"] = res
297
491
  data["event"].set() # notify request thread
298
492
 
299
493
 
300
- def recv_loop(self):
494
+ def __recv_loop(self):
301
495
  print("Recv loop started.")
302
496
  self.ws.run_forever()
303
497
 
carm/carm_dds.py CHANGED
@@ -18,8 +18,11 @@ class ArmDriver:
18
18
  else:
19
19
  self.arm.set_control_mode(1)
20
20
 
21
+ self.arm.set_speed_level(args.speed)
22
+
21
23
  self.pub_joint = messenger.advertise(args.joint_topic, 0)
22
24
  self.pub_end = messenger.advertise(args.end_topic, 0)
25
+ self.pub_joint_ik = messenger.advertise(args.joint_cmd_ik_topic, 0)
23
26
 
24
27
  self.sub_joint = messenger.subscribe(args.joint_cmd_topic, 0, lambda msg:self.joint_callback(msg))
25
28
  self.sub_end = messenger.subscribe(args.end_cmd_topic, 0, lambda msg:self.end_callback(msg))
@@ -28,6 +31,16 @@ class ArmDriver:
28
31
  position = np.frombuffer(msg["position"],dtype=np.float64).tolist()
29
32
  self.arm.track_pose(position)
30
33
 
34
+ if self.args.enable_ik:
35
+ ret = self.arm.invK(position, ref_joints=self.arm.joint_pos)
36
+ if ret["recv"] != "Task_Recieve":
37
+ print("Inverse kinematics failed:", ret)
38
+ return
39
+
40
+ pos = ret["data"]["joint1"]
41
+ self.pub_joint_ik.publish({"header": msg["header"], "position": pos})
42
+
43
+
31
44
  def joint_callback(self, msg):
32
45
  position = np.frombuffer(msg["position"],dtype=np.float64).tolist()
33
46
  self.arm.track_joint(position)
@@ -66,6 +79,7 @@ def driver_main(args):
66
79
  transfer = ros2.Transfer({"node": "carm_driver" + args.device,
67
80
  "publishers":[[args.joint_topic, "sensor_msgs/msg/JointState",10],
68
81
  [args.end_topic, "sensor_msgs/msg/JointState",10],
82
+ [args.joint_cmd_ik_topic, "sensor_msgs/msg/JointState",10],
69
83
  ],
70
84
  "subscriptions":[[args.joint_cmd_topic, "sensor_msgs/msg/JointState",10],
71
85
  [args.end_cmd_topic, "sensor_msgs/msg/JointState",10]]})
@@ -82,7 +96,9 @@ def main():
82
96
  parser.add_argument("--joint_topic", type=str, default="", help="the joints status topic")
83
97
  parser.add_argument("--end_topic", type=str, default="", help="the joints status topic")
84
98
  parser.add_argument("--joint_cmd_topic", type=str, default="", help="the joints cmd topic")
99
+ parser.add_argument("--joint_cmd_ik_topic", type=str, default="", help="the joints cmd ik topic, this means both end_cmd_topic will be transformed with ik")
85
100
  parser.add_argument("--end_cmd_topic", type=str, default="", help="the end cmd topic")
101
+ parser.add_argument("--speed", type=float, default=5.0, help="the speed level")
86
102
 
87
103
  args = parser.parse_args()
88
104
 
@@ -94,6 +110,11 @@ def main():
94
110
  args.joint_cmd_topic = "/"+args.device + "/joints_cmd"
95
111
  if args.end_cmd_topic == "":
96
112
  args.end_cmd_topic = "/"+args.device + "/end_cmd"
113
+ if args.joint_cmd_ik_topic == "":
114
+ args.joint_cmd_ik_topic = "/"+args.device + "/joints_cmd_ik"
115
+ args.__dict__["enable_ik"] = False
116
+ else:
117
+ args.__dict__["enable_ik"] = True
97
118
 
98
119
  if args.cmd == "":
99
120
  driver_main(args)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: carm
3
- Version: 0.0.20250822
3
+ Version: 0.0.20250911
4
4
  Summary: Python interface for cvte arm.
5
5
  Author-email: Yong Zhao <zhaoyong11933@cvte.com>
6
6
  Classifier: Programming Language :: Python :: 3
@@ -0,0 +1,9 @@
1
+ carm/__init__.py,sha256=lfNjXYpO8FjQVunCqiaUzjsNUnyuhcyO8Je0ixh1RJY,24
2
+ carm/carm.py,sha256=yVKWmDov9c69l7ye1jcWwBWluWgarEUbFhU6tSfaVoA,17159
3
+ carm/carm_dds.py,sha256=9IkexhHYrjNx2nzplEa4Kj0KdLayzACPChtIF1G29BM,5137
4
+ carm/carm_ros2.py,sha256=IxQD4pglxCH2Vbfj6mm2DlV8KgkqMnS0k1-mIICV8I8,3709
5
+ carm-0.0.20250911.dist-info/METADATA,sha256=hpgdZE1IgDYSIAC3HU-RMdfVtbVGgYlhcLWKuWyslJA,389
6
+ carm-0.0.20250911.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
7
+ carm-0.0.20250911.dist-info/entry_points.txt,sha256=qsJWC39p-s4UO3KZ8BfV2hikeMX693mahkYbR3w67Lk,76
8
+ carm-0.0.20250911.dist-info/top_level.txt,sha256=nZ1bTssUAnAqc2CMka3erGx_2LsdZrSkqtjNf9Ce_d8,5
9
+ carm-0.0.20250911.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- carm/__init__.py,sha256=lfNjXYpO8FjQVunCqiaUzjsNUnyuhcyO8Je0ixh1RJY,24
2
- carm/carm.py,sha256=8PaBSryLOW9RPzz8o5KUqHiGdPONGiw9HuCJvdWMQgg,10978
3
- carm/carm_dds.py,sha256=1LTwExF3fuRfjI_Ev8P5TVrv62mxiFRwPdEmeyySbM8,4107
4
- carm/carm_ros2.py,sha256=IxQD4pglxCH2Vbfj6mm2DlV8KgkqMnS0k1-mIICV8I8,3709
5
- carm-0.0.20250822.dist-info/METADATA,sha256=RhO7IwJGvpxCOhaOrqDCcSyPYFpnPms2KHqlLqHAJSo,389
6
- carm-0.0.20250822.dist-info/WHEEL,sha256=iAkIy5fosb7FzIOwONchHf19Qu7_1wCWyFNR5gu9nU0,91
7
- carm-0.0.20250822.dist-info/entry_points.txt,sha256=qsJWC39p-s4UO3KZ8BfV2hikeMX693mahkYbR3w67Lk,76
8
- carm-0.0.20250822.dist-info/top_level.txt,sha256=nZ1bTssUAnAqc2CMka3erGx_2LsdZrSkqtjNf9Ce_d8,5
9
- carm-0.0.20250822.dist-info/RECORD,,