hex-zmq-servers 0.3.16__py3-none-any.whl → 1.0.0__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.
- hex_zmq_servers/__init__.py +18 -6
- hex_zmq_servers/cam/berxel/cam_berxel.py +11 -3
- hex_zmq_servers/config/robot_hexarm.json +14 -3
- hex_zmq_servers/mujoco/archer_y6/mujoco_archer_y6.py +44 -20
- hex_zmq_servers/mujoco/e3_desktop/mujoco_e3_desktop.py +68 -35
- hex_zmq_servers/robot/__init__.py +14 -6
- hex_zmq_servers/robot/hexarm/robot_hexarm.py +54 -50
- hex_zmq_servers/robot/hexarm/robot_hexarm_srv.py +2 -3
- hex_zmq_servers/zmq_base.py +10 -3
- hex_zmq_servers-1.0.0.dist-info/METADATA +188 -0
- {hex_zmq_servers-0.3.16.dist-info → hex_zmq_servers-1.0.0.dist-info}/RECORD +14 -14
- {hex_zmq_servers-0.3.16.dist-info → hex_zmq_servers-1.0.0.dist-info}/WHEEL +1 -1
- hex_zmq_servers-0.3.16.dist-info/METADATA +0 -147
- {hex_zmq_servers-0.3.16.dist-info → hex_zmq_servers-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {hex_zmq_servers-0.3.16.dist-info → hex_zmq_servers-1.0.0.dist-info}/top_level.txt +0 -0
hex_zmq_servers/__init__.py
CHANGED
|
@@ -15,7 +15,6 @@ from .zmq_base import HexZMQDummyClient, HexZMQDummyServer
|
|
|
15
15
|
|
|
16
16
|
from .robot import HexRobotBase, HexRobotClientBase, HexRobotServerBase
|
|
17
17
|
from .robot import HexRobotDummy, HexRobotDummyClient, HexRobotDummyServer
|
|
18
|
-
from .robot import HexRobotGello, HexRobotGelloClient, HexRobotGelloServer
|
|
19
18
|
from .robot import HexRobotHexarm, HexRobotHexarmClient, HexRobotHexarmServer, HEXARM_URDF_PATH_DICT
|
|
20
19
|
|
|
21
20
|
from .cam import HexCamBase, HexCamClientBase, HexCamServerBase
|
|
@@ -28,7 +27,6 @@ file_dir = os.path.dirname(os.path.abspath(__file__))
|
|
|
28
27
|
HEX_ZMQ_SERVERS_PATH_DICT = {
|
|
29
28
|
"zmq_dummy": f"{file_dir}/zmq_base.py",
|
|
30
29
|
"robot_dummy": f"{file_dir}/robot/dummy/robot_dummy_srv.py",
|
|
31
|
-
"robot_gello": f"{file_dir}/robot/gello/robot_gello_srv.py",
|
|
32
30
|
"robot_hexarm": f"{file_dir}/robot/hexarm/robot_hexarm_srv.py",
|
|
33
31
|
"cam_dummy": f"{file_dir}/cam/dummy/cam_dummy_srv.py",
|
|
34
32
|
"cam_rgb": f"{file_dir}/cam/rgb/cam_rgb_srv.py",
|
|
@@ -36,7 +34,6 @@ HEX_ZMQ_SERVERS_PATH_DICT = {
|
|
|
36
34
|
HEX_ZMQ_CONFIGS_PATH_DICT = {
|
|
37
35
|
"zmq_dummy": f"{file_dir}/config/zmq_dummy.json",
|
|
38
36
|
"robot_dummy": f"{file_dir}/config/robot_dummy.json",
|
|
39
|
-
"robot_gello": f"{file_dir}/config/robot_gello.json",
|
|
40
37
|
"robot_hexarm": f"{file_dir}/config/robot_hexarm.json",
|
|
41
38
|
"cam_dummy": f"{file_dir}/config/cam_dummy.json",
|
|
42
39
|
"cam_rgb": f"{file_dir}/config/cam_rgb.json",
|
|
@@ -80,9 +77,6 @@ __all__ = [
|
|
|
80
77
|
"HexRobotDummy",
|
|
81
78
|
"HexRobotDummyClient",
|
|
82
79
|
"HexRobotDummyServer",
|
|
83
|
-
"HexRobotGello",
|
|
84
|
-
"HexRobotGelloClient",
|
|
85
|
-
"HexRobotGelloServer",
|
|
86
80
|
"HexRobotHexarm",
|
|
87
81
|
"HexRobotHexarmClient",
|
|
88
82
|
"HexRobotHexarmServer",
|
|
@@ -104,6 +98,7 @@ from importlib.util import find_spec
|
|
|
104
98
|
|
|
105
99
|
_HAS_BERXEL = find_spec("berxel_py_wrapper") is not None
|
|
106
100
|
_HAS_REALSENSE = find_spec("pyrealsense2") is not None
|
|
101
|
+
_HAS_DYNAMIXEL = find_spec("dynamixel-sdk") is not None
|
|
107
102
|
_HAS_MUJOCO = find_spec("mujoco") is not None
|
|
108
103
|
|
|
109
104
|
# Optional: berxel
|
|
@@ -140,6 +135,23 @@ if _HAS_REALSENSE:
|
|
|
140
135
|
"HexCamRealsenseServer",
|
|
141
136
|
])
|
|
142
137
|
|
|
138
|
+
# Optional: dynamixel
|
|
139
|
+
if _HAS_DYNAMIXEL:
|
|
140
|
+
from .robot import HexRobotGello, HexRobotGelloClient, HexRobotGelloServer
|
|
141
|
+
HEX_ZMQ_SERVERS_PATH_DICT.update({
|
|
142
|
+
"robot_gello":
|
|
143
|
+
f"{file_dir}/robot/gello/robot_gello_srv.py",
|
|
144
|
+
})
|
|
145
|
+
HEX_ZMQ_CONFIGS_PATH_DICT.update({
|
|
146
|
+
"robot_gello":
|
|
147
|
+
f"{file_dir}/config/robot_gello.json",
|
|
148
|
+
})
|
|
149
|
+
__all__.extend([
|
|
150
|
+
"HexRobotGello",
|
|
151
|
+
"HexRobotGelloClient",
|
|
152
|
+
"HexRobotGelloServer",
|
|
153
|
+
])
|
|
154
|
+
|
|
143
155
|
# Optional: mujoco
|
|
144
156
|
if _HAS_MUJOCO:
|
|
145
157
|
from .mujoco import HexMujocoBase, HexMujocoClientBase, HexMujocoServerBase
|
|
@@ -47,8 +47,8 @@ class HexCamBerxel(HexCamBase):
|
|
|
47
47
|
|
|
48
48
|
# variables
|
|
49
49
|
# berxel variables
|
|
50
|
-
self.__context = None
|
|
51
|
-
self.__device = None
|
|
50
|
+
self.__context: BerxelHawkContext | None = None
|
|
51
|
+
self.__device: BerxelHawkDevice | None = None
|
|
52
52
|
# camera variables
|
|
53
53
|
self.__intri = np.zeros(4)
|
|
54
54
|
|
|
@@ -214,7 +214,15 @@ class HexCamBerxel(HexCamBase):
|
|
|
214
214
|
return True
|
|
215
215
|
|
|
216
216
|
def __start_stream(self):
|
|
217
|
-
self.
|
|
217
|
+
if self.__serial_number.startswith('P008'):
|
|
218
|
+
self.__device.setSonixAEStatus(False)
|
|
219
|
+
self.__device.setSonixExposureTime(int(self.__exposure // 100))
|
|
220
|
+
else:
|
|
221
|
+
self.__device.setColorExposureGain(self.__exposure, self.__gain)
|
|
222
|
+
self.__device.setDepthElectricCurrent(700)
|
|
223
|
+
self.__device.setDepthAE(False)
|
|
224
|
+
self.__device.setDepthExposure(43)
|
|
225
|
+
self.__device.setDepthGain(1)
|
|
218
226
|
self.__device.setRegistrationEnable(True)
|
|
219
227
|
self.__device.setFrameSync(True)
|
|
220
228
|
while self.__device.setSystemClock() != 0:
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
"device_port": 8439,
|
|
14
14
|
"control_hz": 1000,
|
|
15
15
|
"arm_type": "archer_l6y",
|
|
16
|
-
"use_gripper": true,
|
|
17
16
|
"mit_kp": [
|
|
18
17
|
200.0,
|
|
19
18
|
200.0,
|
|
@@ -21,7 +20,13 @@
|
|
|
21
20
|
75.0,
|
|
22
21
|
15.0,
|
|
23
22
|
15.0,
|
|
24
|
-
20.0
|
|
23
|
+
20.0,
|
|
24
|
+
0.0,
|
|
25
|
+
0.0,
|
|
26
|
+
0.0,
|
|
27
|
+
0.0,
|
|
28
|
+
0.0,
|
|
29
|
+
0.0
|
|
25
30
|
],
|
|
26
31
|
"mit_kd": [
|
|
27
32
|
12.5,
|
|
@@ -30,7 +35,13 @@
|
|
|
30
35
|
6.0,
|
|
31
36
|
0.31,
|
|
32
37
|
0.31,
|
|
33
|
-
1.0
|
|
38
|
+
1.0,
|
|
39
|
+
0.0,
|
|
40
|
+
0.0,
|
|
41
|
+
0.0,
|
|
42
|
+
0.0,
|
|
43
|
+
0.0,
|
|
44
|
+
0.0
|
|
34
45
|
],
|
|
35
46
|
"sens_ts": true
|
|
36
47
|
}
|
|
@@ -76,21 +76,36 @@ class HexMujocoArcherY6(HexMujocoBase):
|
|
|
76
76
|
mujoco.mj_resetData(self.__model, self.__data)
|
|
77
77
|
|
|
78
78
|
# state init
|
|
79
|
-
self.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
79
|
+
self.__state_idx = {
|
|
80
|
+
"robot_arm": ([0, 1, 2, 3, 4, 5]),
|
|
81
|
+
"robot_gripper": [6],
|
|
82
|
+
"obj": [12, 13, 14, 15, 16, 17, 18],
|
|
83
|
+
}
|
|
84
|
+
self.__ctrl_idx = {
|
|
85
|
+
"robot_arm": [0, 1, 2, 3, 4, 5],
|
|
86
|
+
"robot_gripper": [6],
|
|
87
|
+
}
|
|
88
|
+
self.__limit_idx = {
|
|
89
|
+
"robot_arm":
|
|
90
|
+
np.arange(len(self.__state_idx["robot_arm"])).tolist(),
|
|
91
|
+
"robot_gripper":
|
|
92
|
+
(np.arange(len(self.__state_idx["robot_gripper"])) +
|
|
93
|
+
len(self.__state_idx["robot_arm"])).tolist(),
|
|
94
|
+
}
|
|
95
|
+
print(f"self.__limit_idx: {self.__limit_idx}")
|
|
96
|
+
self._limits = self.__model.jnt_range[np.concatenate(
|
|
97
|
+
[self.__state_idx["robot_arm"], self.
|
|
98
|
+
__state_idx["robot_gripper"]]), :].copy().reshape(-1, 1, 2)
|
|
87
99
|
if not self.__tau_ctrl:
|
|
88
100
|
self.__mit_kp = np.ascontiguousarray(np.asarray(self.__mit_kp))
|
|
89
101
|
self.__mit_kd = np.ascontiguousarray(np.asarray(self.__mit_kd))
|
|
90
102
|
self.__mit_ctrl = CtrlUtil()
|
|
91
103
|
self.__gripper_ratio = 1.33 / 1.52
|
|
92
104
|
self._limits[0, -1] *= self.__gripper_ratio
|
|
93
|
-
self._dofs = np.array([
|
|
105
|
+
self._dofs = np.array([
|
|
106
|
+
len(self.__state_idx["robot_arm"]),
|
|
107
|
+
len(self.__state_idx["robot_gripper"])
|
|
108
|
+
])
|
|
94
109
|
keyframe_id = mujoco.mj_name2id(
|
|
95
110
|
self.__model,
|
|
96
111
|
mujoco.mjtObj.mjOBJ_KEY,
|
|
@@ -242,16 +257,25 @@ class HexMujocoArcherY6(HexMujocoBase):
|
|
|
242
257
|
pos = copy.deepcopy(self.__data.qpos)
|
|
243
258
|
vel = copy.deepcopy(self.__data.qvel)
|
|
244
259
|
eff = copy.deepcopy(self.__data.qfrc_actuator)
|
|
245
|
-
pos[self.
|
|
246
|
-
self.
|
|
260
|
+
pos[self.__state_idx["robot_gripper"]] = pos[
|
|
261
|
+
self.__state_idx["robot_gripper"]] * self.__gripper_ratio
|
|
247
262
|
return self.__mujoco_ts() if self.__sens_ts else hex_zmq_ts_now(
|
|
248
263
|
), np.array([
|
|
249
|
-
pos[self.
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
264
|
+
pos[self.__state_idx["robot_arm"] +
|
|
265
|
+
self.__state_idx["robot_gripper"]],
|
|
266
|
+
vel[self.__state_idx["robot_arm"] +
|
|
267
|
+
self.__state_idx["robot_gripper"]],
|
|
268
|
+
eff[self.__state_idx["robot_arm"] +
|
|
269
|
+
self.__state_idx["robot_gripper"]],
|
|
270
|
+
]).T, self.__data.qpos[self.__state_idx["obj"]].copy()
|
|
253
271
|
|
|
254
272
|
def __set_cmds(self, cmds: np.ndarray):
|
|
273
|
+
state_idx = self.__state_idx["robot_arm"] + self.__state_idx[
|
|
274
|
+
"robot_gripper"]
|
|
275
|
+
ctrl_idx = self.__ctrl_idx["robot_arm"] + self.__ctrl_idx[
|
|
276
|
+
"robot_gripper"]
|
|
277
|
+
limit_idx = self.__limit_idx["robot_arm"] + self.__limit_idx[
|
|
278
|
+
"robot_gripper"]
|
|
255
279
|
tau_cmds = None
|
|
256
280
|
if not self.__tau_ctrl:
|
|
257
281
|
cmd_pos = None
|
|
@@ -278,8 +302,8 @@ class HexMujocoArcherY6(HexMujocoBase):
|
|
|
278
302
|
raise ValueError(f"The shape of cmds is invalid: {cmds.shape}")
|
|
279
303
|
tar_pos = self._apply_pos_limits(
|
|
280
304
|
cmd_pos,
|
|
281
|
-
self._limits[0,
|
|
282
|
-
self._limits[0,
|
|
305
|
+
self._limits[limit_idx, 0, 0],
|
|
306
|
+
self._limits[limit_idx, 0, 1],
|
|
283
307
|
)
|
|
284
308
|
tar_pos[-1] /= self.__gripper_ratio
|
|
285
309
|
tau_cmds = self.__mit_ctrl(
|
|
@@ -287,13 +311,13 @@ class HexMujocoArcherY6(HexMujocoBase):
|
|
|
287
311
|
cmd_kd,
|
|
288
312
|
tar_pos,
|
|
289
313
|
tar_vel,
|
|
290
|
-
self.__data.qpos[
|
|
291
|
-
self.__data.qvel[
|
|
314
|
+
self.__data.qpos[state_idx],
|
|
315
|
+
self.__data.qvel[state_idx],
|
|
292
316
|
cmd_tor,
|
|
293
317
|
)
|
|
294
318
|
else:
|
|
295
319
|
tau_cmds = cmds.copy()
|
|
296
|
-
self.__data.ctrl[
|
|
320
|
+
self.__data.ctrl[ctrl_idx] = tau_cmds
|
|
297
321
|
|
|
298
322
|
def __get_rgb(self):
|
|
299
323
|
self.__rgb_cam.update_scene(self.__data, "end_camera")
|
|
@@ -77,18 +77,37 @@ class HexMujocoE3Desktop(HexMujocoBase):
|
|
|
77
77
|
mujoco.mj_resetData(self.__model, self.__data)
|
|
78
78
|
|
|
79
79
|
# state init
|
|
80
|
-
self.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
],
|
|
90
|
-
|
|
91
|
-
|
|
80
|
+
self.__state_idx = {
|
|
81
|
+
"left_arm": [0, 1, 2, 3, 4, 5],
|
|
82
|
+
"left_gripper": [6],
|
|
83
|
+
"right_arm": [12, 13, 14, 15, 16, 17],
|
|
84
|
+
"right_gripper": [18],
|
|
85
|
+
"obj": [24, 25, 26, 27, 28, 29, 30],
|
|
86
|
+
}
|
|
87
|
+
self.__ctrl_idx = {
|
|
88
|
+
"left_arm": [0, 1, 2, 3, 4, 5],
|
|
89
|
+
"left_gripper": [6],
|
|
90
|
+
"right_arm": [7, 8, 9, 10, 11, 12],
|
|
91
|
+
"right_gripper": [13],
|
|
92
|
+
}
|
|
93
|
+
self.__limit_idx = {
|
|
94
|
+
"left_arm":
|
|
95
|
+
np.arange(len(self.__state_idx["left_arm"])).tolist(),
|
|
96
|
+
"left_gripper": (np.arange(len(self.__state_idx["left_gripper"])) +
|
|
97
|
+
len(self.__state_idx["left_arm"])).tolist(),
|
|
98
|
+
"right_arm": (np.arange(len(self.__state_idx["right_arm"])) +
|
|
99
|
+
len(self.__state_idx["left_arm"]) +
|
|
100
|
+
len(self.__state_idx["left_gripper"])).tolist(),
|
|
101
|
+
"right_gripper":
|
|
102
|
+
(np.arange(len(self.__state_idx["right_gripper"])) +
|
|
103
|
+
len(self.__state_idx["left_arm"]) +
|
|
104
|
+
len(self.__state_idx["left_gripper"]) +
|
|
105
|
+
len(self.__state_idx["right_arm"])).tolist(),
|
|
106
|
+
}
|
|
107
|
+
self._limits = self.__model.jnt_range[np.concatenate([
|
|
108
|
+
self.__state_idx["left_arm"], self.__state_idx["left_gripper"],
|
|
109
|
+
self.__state_idx["right_arm"], self.__state_idx["right_gripper"]
|
|
110
|
+
]), :].copy().reshape(-1, 1, 2)
|
|
92
111
|
if not self.__tau_ctrl:
|
|
93
112
|
self.__mit_kp = np.ascontiguousarray(np.asarray(self.__mit_kp))
|
|
94
113
|
self.__mit_kd = np.ascontiguousarray(np.asarray(self.__mit_kd))
|
|
@@ -97,8 +116,10 @@ class HexMujocoE3Desktop(HexMujocoBase):
|
|
|
97
116
|
self._limits[0, -1] *= self.__gripper_ratio
|
|
98
117
|
self._limits[1, -1] *= self.__gripper_ratio
|
|
99
118
|
self._dofs = np.array([
|
|
100
|
-
len(self.
|
|
101
|
-
len(self.
|
|
119
|
+
len(self.__state_idx["left_arm"]),
|
|
120
|
+
len(self.__state_idx["left_gripper"]),
|
|
121
|
+
len(self.__state_idx["right_arm"]),
|
|
122
|
+
len(self.__state_idx["right_gripper"]),
|
|
102
123
|
])
|
|
103
124
|
keyframe_id = mujoco.mj_name2id(
|
|
104
125
|
self.__model,
|
|
@@ -347,35 +368,47 @@ class HexMujocoE3Desktop(HexMujocoBase):
|
|
|
347
368
|
pos = copy.deepcopy(self.__data.qpos)
|
|
348
369
|
vel = copy.deepcopy(self.__data.qvel)
|
|
349
370
|
eff = copy.deepcopy(self.__data.qfrc_actuator)
|
|
350
|
-
pos[self.
|
|
351
|
-
self.
|
|
352
|
-
pos[self.
|
|
353
|
-
self.
|
|
371
|
+
pos[self.__state_idx["left_gripper"]] = pos[
|
|
372
|
+
self.__state_idx["left_gripper"]] * self.__gripper_ratio
|
|
373
|
+
pos[self.__state_idx["right_gripper"]] = pos[
|
|
374
|
+
self.__state_idx["right_gripper"]] * self.__gripper_ratio
|
|
354
375
|
return self.__mujoco_ts() if self.__sens_ts else hex_zmq_ts_now(
|
|
355
376
|
), np.array([
|
|
356
|
-
pos[self.
|
|
357
|
-
|
|
358
|
-
|
|
377
|
+
pos[self.__state_idx["left_arm"] +
|
|
378
|
+
self.__state_idx["left_gripper"]],
|
|
379
|
+
vel[self.__state_idx["left_arm"] +
|
|
380
|
+
self.__state_idx["left_gripper"]],
|
|
381
|
+
eff[self.__state_idx["left_arm"] +
|
|
382
|
+
self.__state_idx["left_gripper"]],
|
|
359
383
|
]).T, np.array([
|
|
360
|
-
pos[self.
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
384
|
+
pos[self.__state_idx["right_arm"] +
|
|
385
|
+
self.__state_idx["right_gripper"]],
|
|
386
|
+
vel[self.__state_idx["right_arm"] +
|
|
387
|
+
self.__state_idx["right_gripper"]],
|
|
388
|
+
eff[self.__state_idx["right_arm"] +
|
|
389
|
+
self.__state_idx["right_gripper"]],
|
|
390
|
+
]).T, self.__data.qpos[self.__state_idx["obj"]].copy()
|
|
364
391
|
|
|
365
392
|
def __set_cmds(self, cmds: np.ndarray, robot_name: str):
|
|
366
|
-
|
|
367
|
-
|
|
393
|
+
state_idx = None
|
|
394
|
+
ctrl_idx = None
|
|
368
395
|
limit_idx = None
|
|
369
396
|
if robot_name == "left":
|
|
370
|
-
ctrl_idx = self.
|
|
397
|
+
ctrl_idx = self.__ctrl_idx["left_arm"] + self.__ctrl_idx[
|
|
398
|
+
"left_gripper"]
|
|
371
399
|
if not self.__tau_ctrl:
|
|
372
|
-
state_idx = self.
|
|
373
|
-
|
|
400
|
+
state_idx = self.__state_idx["left_arm"] + self.__state_idx[
|
|
401
|
+
"left_gripper"]
|
|
402
|
+
limit_idx = self.__limit_idx["left_arm"] + self.__limit_idx[
|
|
403
|
+
"left_gripper"]
|
|
374
404
|
elif robot_name == "right":
|
|
375
|
-
ctrl_idx = self.
|
|
405
|
+
ctrl_idx = self.__ctrl_idx["right_arm"] + self.__ctrl_idx[
|
|
406
|
+
"right_gripper"]
|
|
376
407
|
if not self.__tau_ctrl:
|
|
377
|
-
state_idx = self.
|
|
378
|
-
|
|
408
|
+
state_idx = self.__state_idx["right_arm"] + self.__state_idx[
|
|
409
|
+
"right_gripper"]
|
|
410
|
+
limit_idx = self.__limit_idx["right_arm"] + self.__limit_idx[
|
|
411
|
+
"right_gripper"]
|
|
379
412
|
else:
|
|
380
413
|
raise ValueError(f"unknown robot name: {robot_name}")
|
|
381
414
|
tau_cmds = None
|
|
@@ -404,8 +437,8 @@ class HexMujocoE3Desktop(HexMujocoBase):
|
|
|
404
437
|
raise ValueError(f"The shape of cmds is invalid: {cmds.shape}")
|
|
405
438
|
tar_pos = self._apply_pos_limits(
|
|
406
439
|
cmd_pos,
|
|
407
|
-
self._limits[limit_idx,
|
|
408
|
-
self._limits[limit_idx,
|
|
440
|
+
self._limits[limit_idx, 0, 0],
|
|
441
|
+
self._limits[limit_idx, 0, 1],
|
|
409
442
|
)
|
|
410
443
|
tar_pos[-1] /= self.__gripper_ratio
|
|
411
444
|
tau_cmds = self.__mit_ctrl(
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
|
|
9
9
|
from .robot_base import HexRobotBase, HexRobotClientBase, HexRobotServerBase
|
|
10
10
|
from .dummy import HexRobotDummy, HexRobotDummyClient, HexRobotDummyServer
|
|
11
|
-
from .gello import HexRobotGello, HexRobotGelloClient, HexRobotGelloServer
|
|
12
11
|
from .hexarm import HexRobotHexarm, HexRobotHexarmClient, HexRobotHexarmServer, HEXARM_URDF_PATH_DICT
|
|
13
12
|
|
|
14
13
|
__all__ = [
|
|
@@ -25,13 +24,22 @@ __all__ = [
|
|
|
25
24
|
"HexRobotDummyClient",
|
|
26
25
|
"HexRobotDummyServer",
|
|
27
26
|
|
|
28
|
-
# gello
|
|
29
|
-
"HexRobotGello",
|
|
30
|
-
"HexRobotGelloClient",
|
|
31
|
-
"HexRobotGelloServer",
|
|
32
|
-
|
|
33
27
|
# hexarm
|
|
34
28
|
"HexRobotHexarm",
|
|
35
29
|
"HexRobotHexarmClient",
|
|
36
30
|
"HexRobotHexarmServer",
|
|
37
31
|
]
|
|
32
|
+
|
|
33
|
+
# Check optional dependencies availability
|
|
34
|
+
from importlib.util import find_spec
|
|
35
|
+
|
|
36
|
+
_HAS_DYNAMIXEL = find_spec("dynamixel-sdk") is not None
|
|
37
|
+
|
|
38
|
+
# Optional: dynamixel
|
|
39
|
+
if _HAS_DYNAMIXEL:
|
|
40
|
+
from .gello import HexRobotGello, HexRobotGelloClient, HexRobotGelloServer
|
|
41
|
+
__all__.extend([
|
|
42
|
+
"HexRobotGello",
|
|
43
|
+
"HexRobotGelloClient",
|
|
44
|
+
"HexRobotGelloServer",
|
|
45
|
+
])
|
|
@@ -18,7 +18,7 @@ from ...zmq_base import (
|
|
|
18
18
|
HexRate,
|
|
19
19
|
)
|
|
20
20
|
from ...hex_launch import hex_log, HEX_LOG_LEVEL
|
|
21
|
-
from hex_device import HexDeviceApi,
|
|
21
|
+
from hex_device import HexDeviceApi, Arm, Hands
|
|
22
22
|
from hex_device.motor_base import CommandType
|
|
23
23
|
|
|
24
24
|
ROBOT_CONFIG = {
|
|
@@ -26,7 +26,6 @@ ROBOT_CONFIG = {
|
|
|
26
26
|
"device_port": 8439,
|
|
27
27
|
"control_hz": 250,
|
|
28
28
|
"arm_type": "archer_y6",
|
|
29
|
-
"use_gripper": True,
|
|
30
29
|
"mit_kp": [200.0, 200.0, 200.0, 75.0, 15.0, 15.0, 20.0],
|
|
31
30
|
"mit_kd": [12.5, 12.5, 12.5, 6.0, 0.31, 0.31, 1.0],
|
|
32
31
|
"sens_ts": True,
|
|
@@ -37,6 +36,7 @@ HEX_DEVICE_TYPE_DICT = {
|
|
|
37
36
|
"archer_d6y": 16,
|
|
38
37
|
"archer_l6y": 17,
|
|
39
38
|
"firefly_y6": 27,
|
|
39
|
+
"hello": 26,
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
|
|
@@ -54,7 +54,6 @@ class HexRobotHexarm(HexRobotBase):
|
|
|
54
54
|
device_port = robot_config["device_port"]
|
|
55
55
|
control_hz = robot_config["control_hz"]
|
|
56
56
|
arm_type = HEX_DEVICE_TYPE_DICT[robot_config["arm_type"]]
|
|
57
|
-
use_gripper = robot_config["use_gripper"]
|
|
58
57
|
self.__sens_ts = robot_config["sens_ts"]
|
|
59
58
|
except KeyError as ke:
|
|
60
59
|
missing_key = ke.args[0]
|
|
@@ -73,8 +72,8 @@ class HexRobotHexarm(HexRobotBase):
|
|
|
73
72
|
# variables
|
|
74
73
|
# hex_arm variables
|
|
75
74
|
self.__hex_api: HexDeviceApi | None = None
|
|
76
|
-
self.
|
|
77
|
-
self.__gripper:
|
|
75
|
+
self.__arm: Arm | None = None
|
|
76
|
+
self.__gripper: Hands | None = None
|
|
78
77
|
|
|
79
78
|
# buffer
|
|
80
79
|
self.__arm_state_buffer: dict | None = None
|
|
@@ -90,40 +89,46 @@ class HexRobotHexarm(HexRobotBase):
|
|
|
90
89
|
while self.__hex_api.find_device_by_robot_type(arm_type) is None:
|
|
91
90
|
print("\033[33mArm not found\033[0m")
|
|
92
91
|
time.sleep(1)
|
|
93
|
-
self.
|
|
94
|
-
self.
|
|
95
|
-
self.__arm_dofs = len(self.__arm_archer)
|
|
96
|
-
self._limits = self.__arm_archer.get_joint_limits()
|
|
92
|
+
self.__arm = self.__hex_api.find_device_by_robot_type(arm_type)
|
|
93
|
+
self.__arm.start()
|
|
97
94
|
|
|
98
95
|
# try to open gripper
|
|
99
|
-
self.
|
|
100
|
-
self.__gripper
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
96
|
+
self.__gripper = self.__hex_api.find_optional_device_by_id(1)
|
|
97
|
+
if self.__gripper is None:
|
|
98
|
+
print("\033[33mGripper not found\033[0m")
|
|
99
|
+
|
|
100
|
+
# variables init
|
|
101
|
+
arm_dofs = len(self.__arm)
|
|
102
|
+
self._dofs = [arm_dofs]
|
|
103
|
+
self._limits = np.array(self.__arm.get_joint_limits()).reshape(-1, 3, 2)
|
|
104
|
+
self.__motor_idx = {"robot_arm": np.arange(arm_dofs).tolist()}
|
|
105
|
+
if self.__gripper is not None:
|
|
106
|
+
gripper_dofs = len(self.__gripper)
|
|
107
|
+
self._dofs.append(gripper_dofs)
|
|
108
|
+
gripper_limits = np.array(self.__gripper.get_joint_limits()).reshape(-1, 3, 2)
|
|
109
|
+
self._limits = np.concatenate([self._limits, gripper_limits], axis=0)
|
|
110
|
+
self.__motor_idx["robot_gripper"] = (np.arange(gripper_dofs) +
|
|
111
|
+
arm_dofs).tolist()
|
|
108
112
|
|
|
109
113
|
# modify variables
|
|
110
|
-
self._dofs =
|
|
114
|
+
self._dofs = np.array(self._dofs)
|
|
115
|
+
self._dofs_sum = self._dofs.sum()
|
|
111
116
|
self._limits = np.ascontiguousarray(np.asarray(self._limits)).reshape(
|
|
112
|
-
self.
|
|
117
|
+
self._dofs_sum, 3, 2)
|
|
113
118
|
self.__mit_kp = np.ascontiguousarray(np.asarray(self.__mit_kp))
|
|
114
119
|
self.__mit_kd = np.ascontiguousarray(np.asarray(self.__mit_kd))
|
|
115
|
-
if self.__mit_kp.shape[0] < self.
|
|
116
|
-
0] < self.
|
|
120
|
+
if self.__mit_kp.shape[0] < self._dofs_sum or self.__mit_kd.shape[
|
|
121
|
+
0] < self._dofs_sum:
|
|
117
122
|
raise ValueError(
|
|
118
123
|
"The length of mit_kp and mit_kd must be greater than or equal to the number of motors"
|
|
119
124
|
)
|
|
120
|
-
elif self.__mit_kp.shape[0] > self.
|
|
121
|
-
0] > self.
|
|
125
|
+
elif self.__mit_kp.shape[0] > self._dofs_sum or self.__mit_kd.shape[
|
|
126
|
+
0] > self._dofs_sum:
|
|
122
127
|
print(
|
|
123
128
|
f"\033[33mThe length of mit_kp and mit_kd is greater than the number of motors\033[0m"
|
|
124
129
|
)
|
|
125
|
-
self.__mit_kp = self.__mit_kp[:self.
|
|
126
|
-
self.__mit_kd = self.__mit_kd[:self.
|
|
130
|
+
self.__mit_kp = self.__mit_kp[:self._dofs_sum]
|
|
131
|
+
self.__mit_kd = self.__mit_kd[:self._dofs_sum]
|
|
127
132
|
|
|
128
133
|
# start work loop
|
|
129
134
|
self._working.set()
|
|
@@ -167,13 +172,12 @@ class HexRobotHexarm(HexRobotBase):
|
|
|
167
172
|
self.close()
|
|
168
173
|
|
|
169
174
|
def __get_states(self) -> tuple[np.ndarray | None, dict | None]:
|
|
170
|
-
if self.
|
|
175
|
+
if self.__arm is None:
|
|
171
176
|
return None, None
|
|
172
177
|
|
|
173
178
|
# (arm_dofs, 3) # pos vel eff
|
|
174
179
|
if self.__arm_state_buffer is None:
|
|
175
|
-
self.__arm_state_buffer = self.
|
|
176
|
-
)
|
|
180
|
+
self.__arm_state_buffer = self.__arm.get_simple_motor_status()
|
|
177
181
|
|
|
178
182
|
# (gripper_dofs, 3) # pos vel eff
|
|
179
183
|
if self.__gripper is not None and self.__gripper_state_buffer is None:
|
|
@@ -220,24 +224,24 @@ class HexRobotHexarm(HexRobotBase):
|
|
|
220
224
|
# [[pos_0, tor_0], ..., [pos_n, tor_n]]
|
|
221
225
|
# cmds: (n, 5)
|
|
222
226
|
# [[pos_0, vel_0, tor_0, kp_0, kd_0], ..., [pos_n, vel_n, tor_n, kp_n, kd_n]]
|
|
223
|
-
if self.
|
|
227
|
+
if self.__arm is None:
|
|
224
228
|
print("\033[91mArm not found\033[0m")
|
|
225
229
|
return False
|
|
226
230
|
|
|
227
|
-
if cmds.shape[0] < self.
|
|
231
|
+
if cmds.shape[0] < self._dofs_sum:
|
|
228
232
|
print(
|
|
229
233
|
"\033[91mThe length of joint_angles must be greater than or equal to the number of motors\033[0m"
|
|
230
234
|
)
|
|
231
235
|
return False
|
|
232
|
-
elif cmds.shape[0] > self.
|
|
236
|
+
elif cmds.shape[0] > self._dofs_sum:
|
|
233
237
|
print(
|
|
234
238
|
f"\033[33mThe length of joint_angles is greater than the number of motors\033[0m"
|
|
235
239
|
)
|
|
236
|
-
cmds = cmds[:self.
|
|
240
|
+
cmds = cmds[:self._dofs_sum]
|
|
237
241
|
|
|
238
242
|
cmd_pos = None
|
|
239
|
-
tar_vel = np.zeros(self.
|
|
240
|
-
cmd_tor = np.zeros(self.
|
|
243
|
+
tar_vel = np.zeros(self._dofs_sum)
|
|
244
|
+
cmd_tor = np.zeros(self._dofs_sum)
|
|
241
245
|
cmd_kp = self.__mit_kp.copy()
|
|
242
246
|
cmd_kd = self.__mit_kd.copy()
|
|
243
247
|
if len(cmds.shape) == 1:
|
|
@@ -263,25 +267,25 @@ class HexRobotHexarm(HexRobotBase):
|
|
|
263
267
|
)
|
|
264
268
|
|
|
265
269
|
# arm
|
|
266
|
-
|
|
267
|
-
tar_pos[
|
|
268
|
-
tar_vel[
|
|
269
|
-
cmd_tor[
|
|
270
|
-
cmd_kp[
|
|
271
|
-
cmd_kd[
|
|
270
|
+
arm_cmd = self.__arm.construct_mit_command(
|
|
271
|
+
tar_pos[self.__motor_idx["robot_arm"]],
|
|
272
|
+
tar_vel[self.__motor_idx["robot_arm"]],
|
|
273
|
+
cmd_tor[self.__motor_idx["robot_arm"]],
|
|
274
|
+
cmd_kp[self.__motor_idx["robot_arm"]],
|
|
275
|
+
cmd_kd[self.__motor_idx["robot_arm"]],
|
|
272
276
|
)
|
|
273
|
-
self.
|
|
277
|
+
self.__arm.motor_command(CommandType.MIT, arm_cmd)
|
|
274
278
|
|
|
275
279
|
# gripper
|
|
276
280
|
if self.__gripper is not None:
|
|
277
|
-
|
|
278
|
-
tar_pos[self.
|
|
279
|
-
tar_vel[self.
|
|
280
|
-
cmd_tor[self.
|
|
281
|
-
cmd_kp[self.
|
|
282
|
-
cmd_kd[self.
|
|
281
|
+
gripper_cmd = self.__gripper.construct_mit_command(
|
|
282
|
+
tar_pos[self.__motor_idx["robot_gripper"]],
|
|
283
|
+
tar_vel[self.__motor_idx["robot_gripper"]],
|
|
284
|
+
cmd_tor[self.__motor_idx["robot_gripper"]],
|
|
285
|
+
cmd_kp[self.__motor_idx["robot_gripper"]],
|
|
286
|
+
cmd_kd[self.__motor_idx["robot_gripper"]],
|
|
283
287
|
)
|
|
284
|
-
self.__gripper.motor_command(CommandType.MIT,
|
|
288
|
+
self.__gripper.motor_command(CommandType.MIT, gripper_cmd)
|
|
285
289
|
|
|
286
290
|
return True
|
|
287
291
|
|
|
@@ -289,6 +293,6 @@ class HexRobotHexarm(HexRobotBase):
|
|
|
289
293
|
if not self._working.is_set():
|
|
290
294
|
return
|
|
291
295
|
self._working.clear()
|
|
292
|
-
self.
|
|
296
|
+
self.__arm.stop()
|
|
293
297
|
self.__hex_api.close()
|
|
294
298
|
hex_log(HEX_LOG_LEVEL["info"], "HexRobotHexarm closed")
|
|
@@ -36,9 +36,8 @@ ROBOT_CONFIG = {
|
|
|
36
36
|
"device_port": 8439,
|
|
37
37
|
"control_hz": 250,
|
|
38
38
|
"arm_type": "archer_l6y",
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"mit_kd": [12.5, 12.5, 12.5, 6.0, 0.31, 0.31, 0.0],
|
|
39
|
+
"mit_kp": [200.0, 200.0, 200.0, 75.0, 15.0, 15.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
40
|
+
"mit_kd": [12.5, 12.5, 12.5, 6.0, 0.31, 0.31, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
|
|
42
41
|
"sens_ts": True,
|
|
43
42
|
}
|
|
44
43
|
|
hex_zmq_servers/zmq_base.py
CHANGED
|
@@ -500,11 +500,16 @@ class HexZMQDummyClient(HexZMQClientBase):
|
|
|
500
500
|
net_config: dict = NET_CONFIG,
|
|
501
501
|
):
|
|
502
502
|
HexZMQClientBase.__init__(self, net_config)
|
|
503
|
-
|
|
503
|
+
self._wait_for_working()
|
|
504
|
+
|
|
504
505
|
def single_test(self):
|
|
505
506
|
resp_hdr, resp_buf = self.request({"cmd": "test"})
|
|
506
|
-
|
|
507
|
-
|
|
507
|
+
return resp_hdr, resp_buf
|
|
508
|
+
|
|
509
|
+
def _recv_loop(self):
|
|
510
|
+
rate = HexRate(500)
|
|
511
|
+
while self._recv_flag:
|
|
512
|
+
rate.sleep()
|
|
508
513
|
|
|
509
514
|
|
|
510
515
|
class HexZMQDummyServer(HexZMQServerBase):
|
|
@@ -524,6 +529,8 @@ class HexZMQDummyServer(HexZMQServerBase):
|
|
|
524
529
|
self.close()
|
|
525
530
|
|
|
526
531
|
def _process_request(self, recv_hdr: dict, recv_buf: np.ndarray):
|
|
532
|
+
if recv_hdr["cmd"] == "is_working":
|
|
533
|
+
return self.no_ts_hdr(recv_hdr, True), None
|
|
527
534
|
if recv_hdr["cmd"] == "test":
|
|
528
535
|
print("test received")
|
|
529
536
|
print(f"recv_hdr: {recv_hdr}")
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: hex_zmq_servers
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: HEXFELLOW ZMQ Servers
|
|
5
|
+
Author-email: Dong Zhaorui <joray.dong@hexfellow.com>
|
|
6
|
+
Maintainer-email: jecjune <zejun.chen@hexfellow.com>, Dong Zhaorui <joray.dong@hexfellow.com>
|
|
7
|
+
License-Expression: Apache-2.0
|
|
8
|
+
Project-URL: Homepage, https://github.com/hexfellow/hex_zmq_servers
|
|
9
|
+
Project-URL: Repository, https://github.com/hexfellow/hex_zmq_servers.git
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/hexfellow/hex_zmq_servers/issues
|
|
11
|
+
Project-URL: Documentation, https://github.com/hexfellow/hex_zmq_servers/wiki
|
|
12
|
+
Keywords: hex_zmq_servers
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: pyzmq>=27.0.1
|
|
26
|
+
Requires-Dist: hex_device<1.4.0,>=1.3.14
|
|
27
|
+
Requires-Dist: hex_robo_utils<0.3.0,>=0.2.0
|
|
28
|
+
Requires-Dist: opencv-python>=4.2
|
|
29
|
+
Provides-Extra: berxel
|
|
30
|
+
Requires-Dist: berxel_py_wrapper>=2.0.182; extra == "berxel"
|
|
31
|
+
Provides-Extra: realsense
|
|
32
|
+
Requires-Dist: pyrealsense2>=2.56.5.9235; extra == "realsense"
|
|
33
|
+
Provides-Extra: dynamixel
|
|
34
|
+
Requires-Dist: dynamixel-sdk==3.8.4; extra == "dynamixel"
|
|
35
|
+
Provides-Extra: mujoco
|
|
36
|
+
Requires-Dist: mujoco>=3.3.3; extra == "mujoco"
|
|
37
|
+
Provides-Extra: all
|
|
38
|
+
Requires-Dist: berxel_py_wrapper>=2.0.182; extra == "all"
|
|
39
|
+
Requires-Dist: pyrealsense2>=2.56.5.9235; extra == "all"
|
|
40
|
+
Requires-Dist: dynamixel-sdk==3.8.4; extra == "all"
|
|
41
|
+
Requires-Dist: mujoco>=3.3.3; extra == "all"
|
|
42
|
+
Dynamic: license-file
|
|
43
|
+
|
|
44
|
+
<h1 align="center">HEXFELLOW ZMQ SERVERS</h1>
|
|
45
|
+
|
|
46
|
+
<p align="center">
|
|
47
|
+
<a href="https://github.com/hexfellow/hex_zmq_servers/stargazers">
|
|
48
|
+
<img src="https://img.shields.io/github/stars/hexfellow/hex_zmq_servers?style=flat-square&logo=github" />
|
|
49
|
+
</a>
|
|
50
|
+
<a href="https://github.com/hexfellow/hex_zmq_servers/forks">
|
|
51
|
+
<img src="https://img.shields.io/github/forks/hexfellow/hex_zmq_servers?style=flat-square&logo=github" />
|
|
52
|
+
</a>
|
|
53
|
+
<a href="https://doi.org/10.5281/zenodo.18309954">
|
|
54
|
+
<img src="https://zenodo.org/badge/1088506315.svg" alt="DOI">
|
|
55
|
+
</a>
|
|
56
|
+
|
|
57
|
+
<a href="https://github.com/hexfellow/hex_zmq_servers/issues">
|
|
58
|
+
<img src="https://img.shields.io/github/issues/hexfellow/hex_zmq_servers?style=flat-square&logo=github" />
|
|
59
|
+
</a>
|
|
60
|
+
</p>
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
# 📖 Overview
|
|
65
|
+
|
|
66
|
+
## What is `hex_zmq_servers`
|
|
67
|
+
|
|
68
|
+
`hex_zmq_servers` provides a client–server layer on top of ZeroMQ to control and stream data from HEXFELLOW hardware (robots, RGB/RGB-D cameras) and MuJoCo-based simulators. Servers run device logic and command loops; clients send requests (e.g. `get_rgb`, `get_state`, `set_target`) and receive headers plus optional binary buffers (e.g. images, joint state).
|
|
69
|
+
|
|
70
|
+
## What problem it solves
|
|
71
|
+
|
|
72
|
+
- **Decoupled control**: Run device drivers and control loops in separate processes; clients connect over TCP.
|
|
73
|
+
- **Unified transport**: All devices use the same ZMQ request/response pattern (JSON header + NumPy buffer).
|
|
74
|
+
- **Multi-node management**: `HexLaunch` and `HexNodeConfig` start and monitor multiple server/client nodes from one process.
|
|
75
|
+
|
|
76
|
+
## Target users
|
|
77
|
+
|
|
78
|
+
- Engineers integrating HEXFELLOW robots into their systems.
|
|
79
|
+
- Researchers running experiments with HEXFELLOW robots.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
# 📦 Installation
|
|
84
|
+
|
|
85
|
+
## Requirements
|
|
86
|
+
|
|
87
|
+
- **Python**
|
|
88
|
+
- **OS**: `Linux` / `macOS`
|
|
89
|
+
- **Core dependencies**:
|
|
90
|
+
- `pyzmq`
|
|
91
|
+
- `hex_device`
|
|
92
|
+
- `hex_robo_utils`
|
|
93
|
+
- `opencv-python`
|
|
94
|
+
|
|
95
|
+
Optional device support (install via extras):
|
|
96
|
+
|
|
97
|
+
| Extra | Purpose |
|
|
98
|
+
| ----------- | ----------------------------------------------- |
|
|
99
|
+
| `berxel` | Berxel RGB-D: `berxel_py_wrapper` |
|
|
100
|
+
| `realsense` | RealSense RGB-D: `pyrealsense2` |
|
|
101
|
+
| `dynamixel` | Dynamixel: `dynamixel-sdk` |
|
|
102
|
+
| `mujoco` | MuJoCo sims: `mujoco` |
|
|
103
|
+
| `all` | `berxel` + `dynamixel` + `realsense` + `mujoco` |
|
|
104
|
+
|
|
105
|
+
## Install from PyPI
|
|
106
|
+
|
|
107
|
+
For those who don't need examples, you can install the package from PyPI.
|
|
108
|
+
|
|
109
|
+
- **Full install**: includes all optional devices (Berxel, RealSense, Dynamixel, MuJoCo)
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install hex_zmq_servers[all]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- **Core install**: only the core package (no optional devices)
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
pip install hex_zmq_servers
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Install from Source
|
|
122
|
+
|
|
123
|
+
For those who need examples, you can install the package from source code with examples.
|
|
124
|
+
|
|
125
|
+
**Noet**: We use [**uv**](https://github.com/astral-sh/uv) to manage the Python environment. Please install it first.
|
|
126
|
+
|
|
127
|
+
1. Clone and install in editable mode. The `venv.sh` script expects [uv](https://github.com/astral-sh/uv).
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
git clone https://github.com/hexfellow/hex_zmq_servers.git
|
|
131
|
+
cd hex_zmq_servers
|
|
132
|
+
./venv.sh
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- `./venv.sh` — creates `.venv`, installs `hex_zmq_servers` with `[all]` and `examples/adv/requirements.txt` (e.g. `pygame` for some examples).
|
|
136
|
+
- `./venv.sh --min` — installs the core package only (no optional device extras). Some examples will not run.
|
|
137
|
+
- `./venv.sh --pkg-only` — installs the package only, skips example-related dependencies.
|
|
138
|
+
|
|
139
|
+
2. Activate before running examples:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
source .venv/bin/activate
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
# 📚 Tutorial
|
|
148
|
+
|
|
149
|
+
See [**Tutorial**](docs/tutorial.md) for details of all tutorials.
|
|
150
|
+
|
|
151
|
+
# 📑 API
|
|
152
|
+
|
|
153
|
+
See [**API**](docs/api.md) for details of all APIs.
|
|
154
|
+
|
|
155
|
+
# 💡 Example
|
|
156
|
+
|
|
157
|
+
See [**Example**](docs/example.md) for details of all examples.
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
# 🏷️ Citation
|
|
162
|
+
|
|
163
|
+
If you want to cite this project in your work, you can use the following BibTeX entry:
|
|
164
|
+
|
|
165
|
+
```bibtex
|
|
166
|
+
@software{hex_zmq_servers,
|
|
167
|
+
author = {Dong, Zhaorui},
|
|
168
|
+
title = {Hex ZMQ Servers: A ZeroMQ-Based Embodied AI Communication Framework},
|
|
169
|
+
year = {2025},
|
|
170
|
+
publisher = {Zenodo},
|
|
171
|
+
version = {v1.0.0},
|
|
172
|
+
doi = {10.5281/zenodo.18309960},
|
|
173
|
+
url = {https://doi.org/10.5281/zenodo.18309960}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
# 📄 License
|
|
180
|
+
|
|
181
|
+
Apache License 2.0. See [LICENSE](LICENSE).
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
# 🌟 Star History
|
|
186
|
+
|
|
187
|
+
[](https://star-history.com/#hexfellow/hex_zmq_servers&Date)
|
|
188
|
+
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
hex_zmq_servers/__init__.py,sha256=
|
|
1
|
+
hex_zmq_servers/__init__.py,sha256=YZzC3MnGlyafcB6CShZRG-Jai86rMJ4buZKQ7k6Ap6k,5684
|
|
2
2
|
hex_zmq_servers/device_base.py,sha256=SdRfM4VdJQ_gK-SKjt6ut4fWOR9eYpzmTQb6N6Lx7UI,1264
|
|
3
3
|
hex_zmq_servers/hex_launch.py,sha256=x-xCBhpjgK6yWHGk5o5kYsb5ef2GsdeLr8rX59EJR-0,17700
|
|
4
|
-
hex_zmq_servers/zmq_base.py,sha256=
|
|
4
|
+
hex_zmq_servers/zmq_base.py,sha256=DXLBSCT2VJQG8Y4aXCZEuKrawupOx3sk7H4crz9uXtc,17030
|
|
5
5
|
hex_zmq_servers/cam/__init__.py,sha256=hZ4NTEaO9uxcBua-nyJTpXxmk_GiIAcK2uWz3QQbwvE,1408
|
|
6
6
|
hex_zmq_servers/cam/cam_base.py,sha256=YQ_uwH-ej7qGRr5RqQBDnaS7jCWV66lxAAkWD5c0gLc,6055
|
|
7
7
|
hex_zmq_servers/cam/berxel/__init__.py,sha256=3fK06RWAS_q8a_3z-RxFTv32e99og-4dMZTxH08TOcY,508
|
|
8
|
-
hex_zmq_servers/cam/berxel/cam_berxel.py,sha256=
|
|
8
|
+
hex_zmq_servers/cam/berxel/cam_berxel.py,sha256=TXbG2k3ZrpeuBLk1oqBxihn1FNHegUEuchQ_yIMuSm4,10291
|
|
9
9
|
hex_zmq_servers/cam/berxel/cam_berxel_cli.py,sha256=eoeMWoDZziX3V23v2G76KWhfXSf2jpj4DH2Yo74HvuQ,863
|
|
10
10
|
hex_zmq_servers/cam/berxel/cam_berxel_srv.py,sha256=-Ut2GzAH0-5hFQDDIRcGzYz5On7YiXZ9KfOZrK2w53o,2439
|
|
11
11
|
hex_zmq_servers/cam/dummy/__init__.py,sha256=EziQ8H8d9Vm1AYw4YryIumTXpx-lwEnlfzTl7VAnKHM,499
|
|
@@ -28,12 +28,12 @@ hex_zmq_servers/config/mujoco_archer_y6.json,sha256=cZFUjppqfqxWYrJPYcxqRk7i6DZD
|
|
|
28
28
|
hex_zmq_servers/config/mujoco_e3_desktop.json,sha256=pSh-X4tMPrw5iNk39D9PC5l8xjCs5IB2AvRgOZszDQE,823
|
|
29
29
|
hex_zmq_servers/config/robot_dummy.json,sha256=8dQPI2A0NjC1d6oj_TMoHduoyhynA-cwZ_-7J-QWz-g,3023
|
|
30
30
|
hex_zmq_servers/config/robot_gello.json,sha256=DDICCpSvjUR1yqQFQEmFZ9mrmPt05Zre6Gu28ZMyu5Y,1217
|
|
31
|
-
hex_zmq_servers/config/robot_hexarm.json,sha256=
|
|
31
|
+
hex_zmq_servers/config/robot_hexarm.json,sha256=01QovR0ibsDypVUKL4h_6EVeHG3I8QqfCc-jztH7Mm4,925
|
|
32
32
|
hex_zmq_servers/config/zmq_dummy.json,sha256=yuWindzdHoXv2-nlMEXY1e7W-_vWLYKwe-w8m7eBxWQ,251
|
|
33
33
|
hex_zmq_servers/mujoco/__init__.py,sha256=OKG7aJ2BACpob_DuAvikLiRo4M_3DO9Ffapwen87k_0,870
|
|
34
34
|
hex_zmq_servers/mujoco/mujoco_base.py,sha256=1NXyUn3gBfkww9CKEuvVuzXXJQ-dpYoN_04r4bzZHoU,13632
|
|
35
35
|
hex_zmq_servers/mujoco/archer_y6/__init__.py,sha256=v1vApZ-qBLEcqFjDN0eff5kvkzO3s8QHBO4AitBODfc,559
|
|
36
|
-
hex_zmq_servers/mujoco/archer_y6/mujoco_archer_y6.py,sha256=
|
|
36
|
+
hex_zmq_servers/mujoco/archer_y6/mujoco_archer_y6.py,sha256=9lqQuV8Q43iMlPq1-bKlFaExz77WlTDCiKumyClvKLA,13336
|
|
37
37
|
hex_zmq_servers/mujoco/archer_y6/mujoco_archer_y6_cli.py,sha256=O2BfGf80G-K0CWxjsLrzA_HxqFbe9ED8USjG6OqNNII,2140
|
|
38
38
|
hex_zmq_servers/mujoco/archer_y6/mujoco_archer_y6_srv.py,sha256=nHMaHuauakfaNBy-Y7mYmjYtpWOFCEe8LE93smMhuqk,5211
|
|
39
39
|
hex_zmq_servers/mujoco/archer_y6/model/joint_class.xml,sha256=2VPHALLFrf2PLAiF6KLohigtx8sa757RmYHLgJ-QYFc,995
|
|
@@ -57,7 +57,7 @@ hex_zmq_servers/mujoco/archer_y6/model/assets/gripper_right_link_1.STL,sha256=fj
|
|
|
57
57
|
hex_zmq_servers/mujoco/archer_y6/model/assets/gripper_right_link_2.STL,sha256=ucyn656VxBpRx3sshxLxMPJIUZfNfEQ90P-9b8sR0xg,80284
|
|
58
58
|
hex_zmq_servers/mujoco/archer_y6/model/assets/table_link.STL,sha256=dqFXHRqjZSDyvYHCaJkbmYFsKngZRk1xjg_Zl2_jDc4,684
|
|
59
59
|
hex_zmq_servers/mujoco/e3_desktop/__init__.py,sha256=pMLKpJGBHgc9IU142NcM7ubwdXq9E422k6Giui1A8i4,565
|
|
60
|
-
hex_zmq_servers/mujoco/e3_desktop/mujoco_e3_desktop.py,sha256=
|
|
60
|
+
hex_zmq_servers/mujoco/e3_desktop/mujoco_e3_desktop.py,sha256=uZT5qSzrHO_g5WzhKhAt1t7yLCKGxzDmD5AixTNSyNc,20214
|
|
61
61
|
hex_zmq_servers/mujoco/e3_desktop/mujoco_e3_desktop_cli.py,sha256=_TJAtGNc7Ka6Sm3xiIJeqJ9fiHRecNpwjCxTNzRPbBI,9414
|
|
62
62
|
hex_zmq_servers/mujoco/e3_desktop/mujoco_e3_desktop_srv.py,sha256=uHd0nE8igamBZHC9ouLKKSrg4NWusvk8dQ0HkTpe_OQ,9344
|
|
63
63
|
hex_zmq_servers/mujoco/e3_desktop/model/joint_class.xml,sha256=2VPHALLFrf2PLAiF6KLohigtx8sa757RmYHLgJ-QYFc,995
|
|
@@ -81,7 +81,7 @@ hex_zmq_servers/mujoco/e3_desktop/model/assets/gripper_right_helper_link.STL,sha
|
|
|
81
81
|
hex_zmq_servers/mujoco/e3_desktop/model/assets/gripper_right_link_1.STL,sha256=fjz04vqaBZGOb9GL6N4CVdIR1kOWrsdwh-jo7CRp9pg,28384
|
|
82
82
|
hex_zmq_servers/mujoco/e3_desktop/model/assets/gripper_right_link_2.STL,sha256=ucyn656VxBpRx3sshxLxMPJIUZfNfEQ90P-9b8sR0xg,80284
|
|
83
83
|
hex_zmq_servers/mujoco/e3_desktop/model/assets/table_link.STL,sha256=dqFXHRqjZSDyvYHCaJkbmYFsKngZRk1xjg_Zl2_jDc4,684
|
|
84
|
-
hex_zmq_servers/robot/__init__.py,sha256=
|
|
84
|
+
hex_zmq_servers/robot/__init__.py,sha256=rgAiVl_aSMarD-lqgLznG1bBQvo8xSPjXev1tk3mKsg,1237
|
|
85
85
|
hex_zmq_servers/robot/robot_base.py,sha256=iau5aCLBKuPkRT3E1PTUfHxM2ji41pohVjO1T4Sdd8k,8564
|
|
86
86
|
hex_zmq_servers/robot/dummy/__init__.py,sha256=_lDtPH-JQ1ltPB8JzD7wLu77iwNOgEZ3fJkz8MNKXrw,517
|
|
87
87
|
hex_zmq_servers/robot/dummy/robot_dummy.py,sha256=_dTLajZCO5gjyw0KWedr8VsOTrtSkikcrJ8L2YpEwwQ,2781
|
|
@@ -92,9 +92,9 @@ hex_zmq_servers/robot/gello/robot_gello.py,sha256=AwgMZcADtJR0NPjEYV4_p6v70RCFCw
|
|
|
92
92
|
hex_zmq_servers/robot/gello/robot_gello_cli.py,sha256=1Ik9sDT8Xh2sXtqR46wbJCt7QhnhTKzJeCEzUiiFLtA,751
|
|
93
93
|
hex_zmq_servers/robot/gello/robot_gello_srv.py,sha256=5w95Rgt0p0vxaH2olX40V3h0CwVH8eEtx2U0iTO8cxM,2970
|
|
94
94
|
hex_zmq_servers/robot/hexarm/__init__.py,sha256=-smiJ_bEpmR6kH7GfqDXQzcmaGCpgyuskdl9Hm_trlI,1852
|
|
95
|
-
hex_zmq_servers/robot/hexarm/robot_hexarm.py,sha256=
|
|
95
|
+
hex_zmq_servers/robot/hexarm/robot_hexarm.py,sha256=ciCD2x2_uREJwBavXLe1r_9JOXYd3VzdB4KPb9TI8os,10827
|
|
96
96
|
hex_zmq_servers/robot/hexarm/robot_hexarm_cli.py,sha256=QbFiZFHnmyu5Wfwzj2fMRGqLdXBrS2HcpTGnG-BH_D8,904
|
|
97
|
-
hex_zmq_servers/robot/hexarm/robot_hexarm_srv.py,sha256=
|
|
97
|
+
hex_zmq_servers/robot/hexarm/robot_hexarm_srv.py,sha256=ANmqwl532d0cV9XCA29_55E-HGwrSmvSzQEDG-r-a2Y,2924
|
|
98
98
|
hex_zmq_servers/robot/hexarm/urdf/archer_d6y/empty.urdf,sha256=3qrJq2dQ77k2iz_N2hZQwudvqPsLR_-JUAeCE3M7twQ,7451
|
|
99
99
|
hex_zmq_servers/robot/hexarm/urdf/archer_d6y/gp100.urdf,sha256=VcZw5HNS4DCcD5mI5VvouS-72qrOr7-yzNmch6H2TRo,7468
|
|
100
100
|
hex_zmq_servers/robot/hexarm/urdf/archer_d6y/gp100_handle.urdf,sha256=_5KBK-vHuPZASAsp9EkAFjcY4wsmTS6AioR5iHISuK4,7473
|
|
@@ -111,8 +111,8 @@ hex_zmq_servers/robot/hexarm/urdf/archer_y6/gp100_handle.urdf,sha256=sKZRz97fnz0
|
|
|
111
111
|
hex_zmq_servers/robot/hexarm/urdf/archer_y6/gp100_p050.urdf,sha256=Ayq0xhf8euYWD83hv-Wa2-D0LyOqFrVj-N6W1X1GEHo,7448
|
|
112
112
|
hex_zmq_servers/robot/hexarm/urdf/archer_y6/gp100_p050_handle.urdf,sha256=UjeDE7bZgUMPGJlHOJMJofcEjQ2BRkShCUomxhKUPk0,7445
|
|
113
113
|
hex_zmq_servers/robot/hexarm/urdf/firefly_y6/empty.urdf,sha256=OWNmNHVNP0PvYY8s3jvrNBFAiMEYs3Zxr9NnV1APd8g,7601
|
|
114
|
-
hex_zmq_servers-0.
|
|
115
|
-
hex_zmq_servers-0.
|
|
116
|
-
hex_zmq_servers-0.
|
|
117
|
-
hex_zmq_servers-0.
|
|
118
|
-
hex_zmq_servers-0.
|
|
114
|
+
hex_zmq_servers-1.0.0.dist-info/licenses/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
|
115
|
+
hex_zmq_servers-1.0.0.dist-info/METADATA,sha256=Fun1vJg3YNVw8e30_cH73wk22Slyc7cW_516TTYwyqk,6506
|
|
116
|
+
hex_zmq_servers-1.0.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
117
|
+
hex_zmq_servers-1.0.0.dist-info/top_level.txt,sha256=lPH1DfgMrQOe0Grh8zSZopf6LmnLvb_aStVmZ41PyAg,16
|
|
118
|
+
hex_zmq_servers-1.0.0.dist-info/RECORD,,
|
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: hex_zmq_servers
|
|
3
|
-
Version: 0.3.16
|
|
4
|
-
Summary: HEXFELLOW ZMQ Servers
|
|
5
|
-
Author-email: Dong Zhaorui <joray.dong@hexfellow.com>
|
|
6
|
-
Maintainer-email: jecjune <zejun.chen@hexfellow.com>, Dong Zhaorui <joray.dong@hexfellow.com>
|
|
7
|
-
License-Expression: Apache-2.0
|
|
8
|
-
Project-URL: Homepage, https://github.com/hexfellow/hex_zmq_servers
|
|
9
|
-
Project-URL: Repository, https://github.com/hexfellow/hex_zmq_servers.git
|
|
10
|
-
Project-URL: Bug Tracker, https://github.com/hexfellow/hex_zmq_servers/issues
|
|
11
|
-
Project-URL: Documentation, https://github.com/hexfellow/hex_zmq_servers/wiki
|
|
12
|
-
Keywords: hex_zmq_servers
|
|
13
|
-
Classifier: Development Status :: 4 - Beta
|
|
14
|
-
Classifier: Intended Audience :: Developers
|
|
15
|
-
Classifier: Intended Audience :: Science/Research
|
|
16
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
17
|
-
Classifier: Programming Language :: Python :: 3
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
22
|
-
Requires-Python: >=3.10
|
|
23
|
-
Description-Content-Type: text/markdown
|
|
24
|
-
License-File: LICENSE
|
|
25
|
-
Requires-Dist: pyzmq>=27.0.1
|
|
26
|
-
Requires-Dist: hex_device<1.4.0,>=1.3.1
|
|
27
|
-
Requires-Dist: hex_robo_utils<0.3.0,>=0.2.0
|
|
28
|
-
Requires-Dist: dynamixel-sdk==3.8.4
|
|
29
|
-
Requires-Dist: opencv-python>=4.2
|
|
30
|
-
Provides-Extra: berxel
|
|
31
|
-
Requires-Dist: berxel_py_wrapper>=2.0.182; extra == "berxel"
|
|
32
|
-
Provides-Extra: realsense
|
|
33
|
-
Requires-Dist: pyrealsense2>=2.56.5.9235; extra == "realsense"
|
|
34
|
-
Provides-Extra: mujoco
|
|
35
|
-
Requires-Dist: mujoco>=3.3.3; extra == "mujoco"
|
|
36
|
-
Provides-Extra: all
|
|
37
|
-
Requires-Dist: berxel_py_wrapper>=2.0.182; extra == "all"
|
|
38
|
-
Requires-Dist: mujoco>=3.3.3; extra == "all"
|
|
39
|
-
Requires-Dist: pyrealsense2>=2.56.5.9235; extra == "all"
|
|
40
|
-
Dynamic: license-file
|
|
41
|
-
|
|
42
|
-
# hex_zmq_servers
|
|
43
|
-
|
|
44
|
-
## Introduction
|
|
45
|
-
|
|
46
|
-
**`hex_zmq_servers`** is a comprehensive distributed device control framework based on ZeroMQ, providing efficient client-server communication for HEXFELLOW devices.
|
|
47
|
-
|
|
48
|
-
## Project Structure
|
|
49
|
-
|
|
50
|
-
```bash
|
|
51
|
-
hex_zmq_servers/
|
|
52
|
-
├── hex_zmq_servers/ # Core library
|
|
53
|
-
│ ├── robot/ # Robot devices
|
|
54
|
-
│ ├── cam/ # Camera devices
|
|
55
|
-
│ ├── mujoco/ # Mujoco simulation devices
|
|
56
|
-
│ └── config/ # Default configuration files
|
|
57
|
-
├── examples/ # Example code
|
|
58
|
-
│ ├── basic/ # Basic examples (single device)
|
|
59
|
-
│ └── adv/ # Advanced examples (multi-device coordination)
|
|
60
|
-
└── venv.sh # Virtual environment script
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
## Devices
|
|
64
|
-
|
|
65
|
-
### Robot
|
|
66
|
-
|
|
67
|
-
- **dummy**: Dummy robot, for testing and development
|
|
68
|
-
- **gello**: GELLO robot, based on Dynamixel servo
|
|
69
|
-
- **hexarm**: HexArm robot of HEXFELLOW
|
|
70
|
-
|
|
71
|
-
### Camera
|
|
72
|
-
|
|
73
|
-
- **dummy**: Dummy camera, for testing and development
|
|
74
|
-
- **berxel**: Berxel depth camera, providing RGB and depth images
|
|
75
|
-
|
|
76
|
-
### Mujoco
|
|
77
|
-
|
|
78
|
-
- **archer_y6**: Physical simulation of Archer Y6 robot
|
|
79
|
-
- **e3_desktop**: Physical simulation of E3 Desktop robot
|
|
80
|
-
|
|
81
|
-
## Installation
|
|
82
|
-
|
|
83
|
-
### Install from PyPI
|
|
84
|
-
|
|
85
|
-
1. For those who only want to use the library in their projects, it is recommended to install it from PyPI.
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
pip install hex_zmq_servers[all]
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
2. If you don't want to install the extra dependencies for extra devices, you can run:
|
|
92
|
-
|
|
93
|
-
```bash
|
|
94
|
-
pip install hex_zmq_servers
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
### Install from Source Code
|
|
98
|
-
|
|
99
|
-
1. For those who want to test the examples or contribute to the project, you can install it from source code.
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
git clone https://github.com/hexfellow/hex_zmq_servers.git
|
|
103
|
-
cd hex_zmq_servers
|
|
104
|
-
./venv.sh
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
2. If you don't want to install the extra dependencies for extra devices, you can run:
|
|
108
|
-
|
|
109
|
-
```bash
|
|
110
|
-
git clone https://github.com/hexfellow/hex_zmq_servers.git
|
|
111
|
-
cd hex_zmq_servers
|
|
112
|
-
./venv.sh --min
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
(**Important**) Some examples would not work without the extra dependencies.
|
|
116
|
-
|
|
117
|
-
3. If you don't want to install the examples, you can run:
|
|
118
|
-
|
|
119
|
-
```bash
|
|
120
|
-
git clone https://github.com/hexfellow/hex_zmq_servers.git
|
|
121
|
-
cd hex_zmq_servers
|
|
122
|
-
./venv.sh --pkg-only
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
## Examples
|
|
126
|
-
|
|
127
|
-
There are two types of examples in the project:
|
|
128
|
-
|
|
129
|
-
- **basic/**: Basic examples, showing the usage of a single device
|
|
130
|
-
- **adv/**: Advanced examples, showing multi-device coordination
|
|
131
|
-
|
|
132
|
-
More details please refer to [examples/README.md](examples/README.md)
|
|
133
|
-
|
|
134
|
-
## Contributions
|
|
135
|
-
|
|
136
|
-
Welcome to submit issues and pull requests!
|
|
137
|
-
|
|
138
|
-
## License
|
|
139
|
-
|
|
140
|
-
Apache License 2.0
|
|
141
|
-
|
|
142
|
-
## Contact
|
|
143
|
-
|
|
144
|
-
- Author: [Dong Zhaorui](https://github.com/IBNBlank)
|
|
145
|
-
- Maintainer: [jecjune](https://github.com/Jecjune)
|
|
146
|
-
- GitHub: [hex_zmq_servers](https://github.com/hexfellow/hex_zmq_servers)
|
|
147
|
-
- Issue Tracker: [hex_zmq_servers](https://github.com/hexfellow/hex_zmq_servers/issues)
|
|
File without changes
|
|
File without changes
|