hex-zmq-servers 0.3.9__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 +173 -0
- hex_zmq_servers/cam/__init__.py +52 -0
- hex_zmq_servers/cam/berxel/__init__.py +17 -0
- hex_zmq_servers/cam/berxel/cam_berxel.py +282 -0
- hex_zmq_servers/cam/berxel/cam_berxel_cli.py +33 -0
- hex_zmq_servers/cam/berxel/cam_berxel_srv.py +79 -0
- hex_zmq_servers/cam/cam_base.py +189 -0
- hex_zmq_servers/cam/dummy/__init__.py +17 -0
- hex_zmq_servers/cam/dummy/cam_dummy.py +69 -0
- hex_zmq_servers/cam/dummy/cam_dummy_cli.py +29 -0
- hex_zmq_servers/cam/dummy/cam_dummy_srv.py +68 -0
- hex_zmq_servers/cam/realsense/__init__.py +17 -0
- hex_zmq_servers/cam/realsense/cam_realsense.py +159 -0
- hex_zmq_servers/cam/realsense/cam_realsense_cli.py +33 -0
- hex_zmq_servers/cam/realsense/cam_realsense_srv.py +78 -0
- hex_zmq_servers/cam/rgb/__init__.py +17 -0
- hex_zmq_servers/cam/rgb/cam_rgb.py +135 -0
- hex_zmq_servers/cam/rgb/cam_rgb_cli.py +43 -0
- hex_zmq_servers/cam/rgb/cam_rgb_srv.py +78 -0
- hex_zmq_servers/config/cam_berxel.json +18 -0
- hex_zmq_servers/config/cam_dummy.json +12 -0
- hex_zmq_servers/config/cam_realsense.json +17 -0
- hex_zmq_servers/config/cam_rgb.json +28 -0
- hex_zmq_servers/config/mujoco_archer_y6.json +37 -0
- hex_zmq_servers/config/mujoco_e3_desktop.json +41 -0
- hex_zmq_servers/config/robot_dummy.json +153 -0
- hex_zmq_servers/config/robot_gello.json +66 -0
- hex_zmq_servers/config/robot_hexarm.json +37 -0
- hex_zmq_servers/config/zmq_dummy.json +12 -0
- hex_zmq_servers/device_base.py +44 -0
- hex_zmq_servers/hex_launch.py +489 -0
- hex_zmq_servers/mujoco/__init__.py +28 -0
- hex_zmq_servers/mujoco/archer_y6/__init__.py +17 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/arm_base_link.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/arm_link_1.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/arm_link_2.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/arm_link_3.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/arm_link_4.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/arm_link_5.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/assets.xml +17 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/camera_link.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/gripper_base_link.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/gripper_left_helper_link.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/gripper_left_link_1.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/gripper_left_link_2.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/gripper_right_helper_link.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/gripper_right_link_1.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/gripper_right_link_2.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/assets/table_link.STL +0 -0
- hex_zmq_servers/mujoco/archer_y6/model/robot.xml +95 -0
- hex_zmq_servers/mujoco/archer_y6/model/scene.xml +51 -0
- hex_zmq_servers/mujoco/archer_y6/model/setting.xml +37 -0
- hex_zmq_servers/mujoco/archer_y6/mujoco_archer_y6.py +325 -0
- hex_zmq_servers/mujoco/archer_y6/mujoco_archer_y6_cli.py +71 -0
- hex_zmq_servers/mujoco/archer_y6/mujoco_archer_y6_srv.py +148 -0
- hex_zmq_servers/mujoco/e3_desktop/__init__.py +17 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/arm_base_link.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/arm_link_1.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/arm_link_2.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/arm_link_3.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/arm_link_4.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/arm_link_5.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/assets.xml +18 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/camera_link.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/e3_desktop_base_link.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/gripper_base_link.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/gripper_left_helper_link.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/gripper_left_link_1.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/gripper_left_link_2.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/gripper_right_helper_link.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/gripper_right_link_1.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/gripper_right_link_2.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/assets/table_link.STL +0 -0
- hex_zmq_servers/mujoco/e3_desktop/model/robot.xml +188 -0
- hex_zmq_servers/mujoco/e3_desktop/model/scene.xml +53 -0
- hex_zmq_servers/mujoco/e3_desktop/model/setting.xml +72 -0
- hex_zmq_servers/mujoco/e3_desktop/mujoco_e3_desktop.py +449 -0
- hex_zmq_servers/mujoco/e3_desktop/mujoco_e3_desktop_cli.py +289 -0
- hex_zmq_servers/mujoco/e3_desktop/mujoco_e3_desktop_srv.py +244 -0
- hex_zmq_servers/mujoco/mujoco_base.py +425 -0
- hex_zmq_servers/robot/__init__.py +37 -0
- hex_zmq_servers/robot/dummy/__init__.py +17 -0
- hex_zmq_servers/robot/dummy/robot_dummy.py +94 -0
- hex_zmq_servers/robot/dummy/robot_dummy_cli.py +29 -0
- hex_zmq_servers/robot/dummy/robot_dummy_srv.py +82 -0
- hex_zmq_servers/robot/gello/__init__.py +17 -0
- hex_zmq_servers/robot/gello/robot_gello.py +366 -0
- hex_zmq_servers/robot/gello/robot_gello_cli.py +29 -0
- hex_zmq_servers/robot/gello/robot_gello_srv.py +93 -0
- hex_zmq_servers/robot/hexarm/__init__.py +47 -0
- hex_zmq_servers/robot/hexarm/robot_hexarm.py +292 -0
- hex_zmq_servers/robot/hexarm/robot_hexarm_cli.py +37 -0
- hex_zmq_servers/robot/hexarm/robot_hexarm_srv.py +87 -0
- hex_zmq_servers/robot/hexarm/urdf/archer_l6y/empty.urdf +206 -0
- hex_zmq_servers/robot/hexarm/urdf/archer_l6y/gp100.urdf +206 -0
- hex_zmq_servers/robot/hexarm/urdf/archer_l6y/gp100_handle.urdf +206 -0
- hex_zmq_servers/robot/hexarm/urdf/archer_l6y/gp100_p050.urdf +206 -0
- hex_zmq_servers/robot/hexarm/urdf/archer_l6y/gp100_p050_handle.urdf +206 -0
- hex_zmq_servers/robot/hexarm/urdf/archer_y6/empty.urdf +207 -0
- hex_zmq_servers/robot/hexarm/urdf/archer_y6/gp100.urdf +207 -0
- hex_zmq_servers/robot/hexarm/urdf/archer_y6/gp100_handle.urdf +207 -0
- hex_zmq_servers/robot/hexarm/urdf/archer_y6/gp100_p050.urdf +207 -0
- hex_zmq_servers/robot/hexarm/urdf/archer_y6/gp100_p050_handle.urdf +207 -0
- hex_zmq_servers/robot/robot_base.py +276 -0
- hex_zmq_servers/zmq_base.py +547 -0
- hex_zmq_servers-0.3.9.dist-info/METADATA +147 -0
- hex_zmq_servers-0.3.9.dist-info/RECORD +110 -0
- hex_zmq_servers-0.3.9.dist-info/WHEEL +5 -0
- hex_zmq_servers-0.3.9.dist-info/licenses/LICENSE +201 -0
- hex_zmq_servers-0.3.9.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding:utf-8 -*-
|
|
3
|
+
################################################################
|
|
4
|
+
# Copyright 2025 Dong Zhaorui. All rights reserved.
|
|
5
|
+
# Author: Dong Zhaorui 847235539@qq.com
|
|
6
|
+
# Date : 2025-09-16
|
|
7
|
+
################################################################
|
|
8
|
+
|
|
9
|
+
import threading
|
|
10
|
+
from collections import deque
|
|
11
|
+
from abc import ABC, abstractmethod
|
|
12
|
+
|
|
13
|
+
from .zmq_base import MAX_SEQ_NUM
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HexDeviceBase(ABC):
|
|
17
|
+
|
|
18
|
+
def __init__(self, realtime_mode: bool = False):
|
|
19
|
+
# variables
|
|
20
|
+
self._max_seq_num = MAX_SEQ_NUM
|
|
21
|
+
self._realtime_mode = realtime_mode
|
|
22
|
+
# thread
|
|
23
|
+
self._working = threading.Event()
|
|
24
|
+
|
|
25
|
+
def __del__(self):
|
|
26
|
+
self.close()
|
|
27
|
+
|
|
28
|
+
def is_working(self) -> bool:
|
|
29
|
+
return self._working.is_set()
|
|
30
|
+
|
|
31
|
+
def _wait_for_working(self):
|
|
32
|
+
while not self._working.is_set():
|
|
33
|
+
print("waiting for device to work")
|
|
34
|
+
self._working.wait(0.1)
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def work_loop(self, hex_queues: list[deque | threading.Event]):
|
|
38
|
+
raise NotImplementedError(
|
|
39
|
+
"`work_loop` should be implemented by the child class")
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def close(self):
|
|
43
|
+
raise NotImplementedError(
|
|
44
|
+
"`close` should be implemented by the child class")
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding:utf-8 -*-
|
|
3
|
+
################################################################
|
|
4
|
+
# Copyright 2025 Dong Zhaorui. All rights reserved.
|
|
5
|
+
# Author: Dong Zhaorui 847235539@qq.com
|
|
6
|
+
# Date : 2025-09-25
|
|
7
|
+
################################################################
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import json
|
|
12
|
+
import os, sys, signal, subprocess, threading, time
|
|
13
|
+
import termios
|
|
14
|
+
import importlib.util
|
|
15
|
+
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
HEX_LOG_LEVEL = {
|
|
20
|
+
"info": 0,
|
|
21
|
+
"warn": 1,
|
|
22
|
+
"err": 2,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def hex_log(level: int, message):
|
|
27
|
+
print(f"[{level}] {message}")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def hex_err(message):
|
|
31
|
+
print(message, file=sys.stderr)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def dict_update(dict_raw: dict, dict_new: dict, add_new: bool = False):
|
|
35
|
+
for key, value in dict_new.items():
|
|
36
|
+
if key in dict_raw:
|
|
37
|
+
if isinstance(dict_raw[key], dict) and isinstance(value, dict):
|
|
38
|
+
dict_update(dict_raw[key], value)
|
|
39
|
+
else:
|
|
40
|
+
dict_raw[key] = value
|
|
41
|
+
elif add_new:
|
|
42
|
+
dict_raw[key] = value
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def hex_dict_str(dict_raw: dict, indent: int = 0) -> str:
|
|
46
|
+
print_str = ("\n" + "*" * 50 + "\n") if indent == 0 else ""
|
|
47
|
+
for key, value in dict_raw.items():
|
|
48
|
+
if isinstance(value, dict):
|
|
49
|
+
print_str += f"{' ' * indent * 4}{key}:\n{hex_dict_str(value, indent + 1)}"
|
|
50
|
+
else:
|
|
51
|
+
print_str += f"{' ' * indent * 4}{key}: {value}\n"
|
|
52
|
+
print_str += ("*" * 50) if indent == 0 else ""
|
|
53
|
+
return print_str
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class HexNodeConfig():
|
|
57
|
+
|
|
58
|
+
def __init__(
|
|
59
|
+
self,
|
|
60
|
+
init_params: dict[str, dict] | list[dict] | HexNodeConfig = {},
|
|
61
|
+
):
|
|
62
|
+
len_init_params = len(init_params)
|
|
63
|
+
if isinstance(init_params, list):
|
|
64
|
+
self._cfgs_dict = {cfg["name"]: cfg for cfg in init_params}
|
|
65
|
+
elif isinstance(init_params, dict):
|
|
66
|
+
self._cfgs_dict = init_params
|
|
67
|
+
if len_init_params != len(self._cfgs_dict):
|
|
68
|
+
raise ValueError(f"Invalid init_params: {init_params}")
|
|
69
|
+
elif isinstance(init_params, HexNodeConfig):
|
|
70
|
+
self._cfgs_dict = init_params._cfgs_dict
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(f"Invalid init_params: {init_params}")
|
|
73
|
+
|
|
74
|
+
assert len(self._cfgs_dict
|
|
75
|
+
) == len_init_params, f"Invalid init_params: {init_params}"
|
|
76
|
+
|
|
77
|
+
def __len__(self) -> int:
|
|
78
|
+
return len(self._cfgs_dict)
|
|
79
|
+
|
|
80
|
+
def get_cfgs(self, use_list: bool = True) -> list[dict]:
|
|
81
|
+
if use_list:
|
|
82
|
+
return list(self._cfgs_dict.values())
|
|
83
|
+
else:
|
|
84
|
+
return self._cfgs_dict
|
|
85
|
+
|
|
86
|
+
def add_cfgs(
|
|
87
|
+
self,
|
|
88
|
+
node_cfgs: list[dict] | dict[str, dict] | HexNodeConfig,
|
|
89
|
+
):
|
|
90
|
+
new_cfgs = {}
|
|
91
|
+
if isinstance(node_cfgs, list):
|
|
92
|
+
new_cfgs = {cfg["name"]: cfg for cfg in node_cfgs}
|
|
93
|
+
elif isinstance(node_cfgs, dict):
|
|
94
|
+
new_cfgs = node_cfgs
|
|
95
|
+
elif isinstance(node_cfgs, HexNodeConfig):
|
|
96
|
+
new_cfgs = node_cfgs.get_cfgs(use_list=False)
|
|
97
|
+
else:
|
|
98
|
+
raise ValueError(f"Invalid node_cfgs: {node_cfgs}")
|
|
99
|
+
|
|
100
|
+
dict_update(self._cfgs_dict, new_cfgs, add_new=True)
|
|
101
|
+
|
|
102
|
+
def __str__(self) -> str:
|
|
103
|
+
print_str = f"[HexNodeConfig] Total {len(self._cfgs_dict)} nodes:\n"
|
|
104
|
+
for name in self._cfgs_dict.keys():
|
|
105
|
+
print_str += f" - {name}\n"
|
|
106
|
+
return print_str
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def parse_node_params_dict(
|
|
110
|
+
node_params_dict: dict,
|
|
111
|
+
node_default_params_dict: dict,
|
|
112
|
+
) -> HexNodeConfig:
|
|
113
|
+
node_dict = {}
|
|
114
|
+
for cur_name, cur_default_params in node_default_params_dict.items():
|
|
115
|
+
if cur_name in node_params_dict.keys():
|
|
116
|
+
dict_update(cur_default_params, node_params_dict[cur_name])
|
|
117
|
+
node_dict[cur_name] = cur_default_params
|
|
118
|
+
return HexNodeConfig(node_dict)
|
|
119
|
+
|
|
120
|
+
@staticmethod
|
|
121
|
+
def get_launch_params_cfgs(
|
|
122
|
+
launch_params_dict: dict,
|
|
123
|
+
launch_default_params_dict: dict,
|
|
124
|
+
launch_path_dict: dict,
|
|
125
|
+
) -> HexNodeConfig:
|
|
126
|
+
cfg_list = []
|
|
127
|
+
for launch_name, (launch_path, launch_arg) in launch_path_dict.items():
|
|
128
|
+
node_default_params_dict = launch_default_params_dict.get(
|
|
129
|
+
launch_name, {})
|
|
130
|
+
|
|
131
|
+
node_params_dict = {}
|
|
132
|
+
if launch_name in launch_params_dict.keys():
|
|
133
|
+
node_params_dict = launch_params_dict[launch_name]
|
|
134
|
+
else:
|
|
135
|
+
for node_name in node_default_params_dict.keys():
|
|
136
|
+
if node_name in launch_params_dict.keys():
|
|
137
|
+
node_params_dict[node_name] = launch_params_dict[
|
|
138
|
+
node_name]
|
|
139
|
+
|
|
140
|
+
launch_update_cfg = HexNodeConfig.parse_node_params_dict(
|
|
141
|
+
node_params_dict,
|
|
142
|
+
node_default_params_dict,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
cfg_list.append(
|
|
146
|
+
HexNodeConfig.get_node_cfgs_from_launch(
|
|
147
|
+
launch_path,
|
|
148
|
+
launch_update_cfg,
|
|
149
|
+
launch_arg,
|
|
150
|
+
))
|
|
151
|
+
|
|
152
|
+
final_cfg = HexNodeConfig()
|
|
153
|
+
for cfg in cfg_list:
|
|
154
|
+
# use name as key to make sure every node has a unique name
|
|
155
|
+
final_cfg.add_cfgs(cfg.get_cfgs(use_list=True))
|
|
156
|
+
print(f"final_cfg: {final_cfg}")
|
|
157
|
+
return final_cfg
|
|
158
|
+
|
|
159
|
+
@staticmethod
|
|
160
|
+
def get_node_cfgs_from_launch(
|
|
161
|
+
launch_path: str,
|
|
162
|
+
params: dict | HexNodeConfig = {},
|
|
163
|
+
launch_arg: dict | None = None,
|
|
164
|
+
) -> HexNodeConfig:
|
|
165
|
+
# normalize the path
|
|
166
|
+
launch_path = os.path.abspath(launch_path)
|
|
167
|
+
if not os.path.exists(launch_path):
|
|
168
|
+
raise FileNotFoundError(f"Launch file not found: {launch_path}")
|
|
169
|
+
|
|
170
|
+
# load the module dynamically
|
|
171
|
+
spec = importlib.util.spec_from_file_location("launch_module",
|
|
172
|
+
launch_path)
|
|
173
|
+
if spec is None or spec.loader is None:
|
|
174
|
+
raise ImportError(f"Failed to load module from: {launch_path}")
|
|
175
|
+
|
|
176
|
+
# load module
|
|
177
|
+
launch_module = importlib.util.module_from_spec(spec)
|
|
178
|
+
sys.modules["launch_module"] = launch_module
|
|
179
|
+
spec.loader.exec_module(launch_module)
|
|
180
|
+
|
|
181
|
+
# check if `get_node_cfgs` function exists
|
|
182
|
+
if not hasattr(launch_module, "get_node_cfgs"):
|
|
183
|
+
raise AttributeError(
|
|
184
|
+
f"Function 'get_node_cfgs' not found in {launch_path}")
|
|
185
|
+
|
|
186
|
+
# call `get_node_cfgs` function
|
|
187
|
+
get_node_cfgs_func = getattr(launch_module, "get_node_cfgs")
|
|
188
|
+
if isinstance(params, dict):
|
|
189
|
+
print("params is dict")
|
|
190
|
+
node_cfgs = get_node_cfgs_func(params, launch_arg)
|
|
191
|
+
elif isinstance(params, HexNodeConfig):
|
|
192
|
+
print("params is HexNodeConfig")
|
|
193
|
+
node_cfgs = get_node_cfgs_func(
|
|
194
|
+
params.get_cfgs(use_list=False),
|
|
195
|
+
launch_arg,
|
|
196
|
+
)
|
|
197
|
+
else:
|
|
198
|
+
raise ValueError(f"Invalid params: {params}")
|
|
199
|
+
return node_cfgs
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class HexLaunch:
|
|
203
|
+
|
|
204
|
+
def __init__(
|
|
205
|
+
self,
|
|
206
|
+
node_cfgs: list[dict] | dict[str, dict] | HexNodeConfig,
|
|
207
|
+
log_dir: str = "logs",
|
|
208
|
+
min_level: int = HEX_LOG_LEVEL["warn"],
|
|
209
|
+
):
|
|
210
|
+
if isinstance(node_cfgs, list):
|
|
211
|
+
node_cfgs = HexNodeConfig(node_cfgs)
|
|
212
|
+
elif isinstance(node_cfgs, dict):
|
|
213
|
+
node_cfgs = HexNodeConfig(node_cfgs)
|
|
214
|
+
elif isinstance(node_cfgs, HexNodeConfig):
|
|
215
|
+
node_cfgs = node_cfgs
|
|
216
|
+
else:
|
|
217
|
+
raise ValueError(f"Invalid node_cfgs: {node_cfgs}")
|
|
218
|
+
self.__node_cfgs = node_cfgs.get_cfgs(use_list=True)
|
|
219
|
+
self.__state: dict[str, dict] = {}
|
|
220
|
+
self.__stop_event = threading.Event()
|
|
221
|
+
self.__log_dir = Path(log_dir)
|
|
222
|
+
self.__log_dir.mkdir(parents=True, exist_ok=True)
|
|
223
|
+
self.__min_level = min_level
|
|
224
|
+
# lock state for node messages
|
|
225
|
+
self.__state_lock = threading.Lock()
|
|
226
|
+
self.__shutdown_called = False
|
|
227
|
+
# terminal attrs
|
|
228
|
+
self.__terminal_attrs = None
|
|
229
|
+
self.__record_terminal_attrs()
|
|
230
|
+
|
|
231
|
+
def __record_terminal_attrs(self):
|
|
232
|
+
"""record terminal attrs"""
|
|
233
|
+
try:
|
|
234
|
+
self.__terminal_attrs = termios.tcgetattr(sys.stdin.fileno())
|
|
235
|
+
self.__terminal_attrs[3] |= (termios.ICANON | termios.ECHO)
|
|
236
|
+
try:
|
|
237
|
+
self.__terminal_attrs[6][termios.VMIN] = 1
|
|
238
|
+
self.__terminal_attrs[6][termios.VTIME] = 0
|
|
239
|
+
except Exception:
|
|
240
|
+
pass
|
|
241
|
+
print("[launcher] Terminal settings recorded")
|
|
242
|
+
except Exception as e:
|
|
243
|
+
print(f"[launcher] Failed to record terminal attrs: {e}")
|
|
244
|
+
|
|
245
|
+
def __restore_terminal_attrs(self):
|
|
246
|
+
"""restore terminal attrs"""
|
|
247
|
+
if self.__terminal_attrs is not None:
|
|
248
|
+
try:
|
|
249
|
+
termios.tcsetattr(
|
|
250
|
+
sys.stdin.fileno(),
|
|
251
|
+
termios.TCSADRAIN,
|
|
252
|
+
self.__terminal_attrs,
|
|
253
|
+
)
|
|
254
|
+
print("[launcher] Terminal attrs restored")
|
|
255
|
+
except Exception as e:
|
|
256
|
+
print(f"[launcher] Failed to restore terminal attrs: {e}")
|
|
257
|
+
else:
|
|
258
|
+
print("[launcher] No terminal attrs to restore")
|
|
259
|
+
|
|
260
|
+
def __build_cmd(self, node_cfg: dict):
|
|
261
|
+
# python path
|
|
262
|
+
venv = node_cfg.get("venv") or None
|
|
263
|
+
python_exe = os.path.join(
|
|
264
|
+
venv,
|
|
265
|
+
"bin",
|
|
266
|
+
"python",
|
|
267
|
+
) if venv else sys.executable
|
|
268
|
+
|
|
269
|
+
# node path
|
|
270
|
+
node_path = node_cfg.get("node_path") or None
|
|
271
|
+
if not node_path:
|
|
272
|
+
raise ValueError("node_path is required")
|
|
273
|
+
|
|
274
|
+
# cfg
|
|
275
|
+
cfg_obj = {"name": node_cfg.get("name")}
|
|
276
|
+
cfg_path = node_cfg.get("cfg_path", "") or None
|
|
277
|
+
if cfg_path:
|
|
278
|
+
if not os.path.exists(cfg_path):
|
|
279
|
+
raise FileNotFoundError(f"cfg_path {cfg_path} not found")
|
|
280
|
+
with open(cfg_path, "r", encoding="utf-8") as f:
|
|
281
|
+
cfg_file = json.load(f)
|
|
282
|
+
cfg_obj.update(cfg_file)
|
|
283
|
+
dict_update(cfg_obj, node_cfg.get("cfg", {}))
|
|
284
|
+
cfg_str = json.dumps(cfg_obj)
|
|
285
|
+
|
|
286
|
+
# build cmd
|
|
287
|
+
return [python_exe, "-u", str(node_path)] + ["--cfg", cfg_str]
|
|
288
|
+
|
|
289
|
+
def __stream_printer(
|
|
290
|
+
self,
|
|
291
|
+
prefix: str,
|
|
292
|
+
stream,
|
|
293
|
+
is_stderr: bool,
|
|
294
|
+
logfile_path: Path | None,
|
|
295
|
+
min_level: int,
|
|
296
|
+
):
|
|
297
|
+
logfile = logfile_path.open("a",
|
|
298
|
+
encoding="utf-8") if logfile_path else None
|
|
299
|
+
try:
|
|
300
|
+
while True:
|
|
301
|
+
raw = stream.readline()
|
|
302
|
+
if not raw:
|
|
303
|
+
time.sleep(0.001)
|
|
304
|
+
continue
|
|
305
|
+
line = raw.decode(errors="replace").rstrip("\n")
|
|
306
|
+
|
|
307
|
+
record_flag = True
|
|
308
|
+
print_start, print_end, log_start, log_end = "", "", "", ""
|
|
309
|
+
if not is_stderr:
|
|
310
|
+
# parse level if present
|
|
311
|
+
level = HEX_LOG_LEVEL["info"]
|
|
312
|
+
if line.startswith("[") and "]" in line:
|
|
313
|
+
try:
|
|
314
|
+
lvl = int(line[1:line.index("]")])
|
|
315
|
+
if lvl in HEX_LOG_LEVEL.values():
|
|
316
|
+
level = lvl
|
|
317
|
+
line = line[line.index("]") + 1:].lstrip()
|
|
318
|
+
except Exception:
|
|
319
|
+
level = HEX_LOG_LEVEL["info"]
|
|
320
|
+
|
|
321
|
+
if level < min_level:
|
|
322
|
+
record_flag = False
|
|
323
|
+
|
|
324
|
+
# colored print
|
|
325
|
+
if level == HEX_LOG_LEVEL["info"]:
|
|
326
|
+
color_start, color_end = "", ""
|
|
327
|
+
level_str = "info"
|
|
328
|
+
elif level == HEX_LOG_LEVEL["warn"]:
|
|
329
|
+
color_start, color_end = "\033[33m", "\033[0m"
|
|
330
|
+
level_str = "warn"
|
|
331
|
+
elif level == HEX_LOG_LEVEL["err"]:
|
|
332
|
+
color_start, color_end = "\033[31m", "\033[0m"
|
|
333
|
+
level_str = "err"
|
|
334
|
+
|
|
335
|
+
print_start, print_end = f"{color_start}[{prefix}] ", f"{color_end}"
|
|
336
|
+
log_start, log_end = f"[{level_str}] ", ""
|
|
337
|
+
else:
|
|
338
|
+
print_start, print_end = f"\033[31m[FATAL][{prefix}] ", "\033[0m"
|
|
339
|
+
log_start, log_end = "", ""
|
|
340
|
+
|
|
341
|
+
print(f"{print_start}{line}{print_end}", flush=True)
|
|
342
|
+
if logfile and record_flag:
|
|
343
|
+
logfile.write(f"{log_start}{line}{log_end}\n")
|
|
344
|
+
logfile.flush()
|
|
345
|
+
finally:
|
|
346
|
+
if logfile:
|
|
347
|
+
logfile.close()
|
|
348
|
+
|
|
349
|
+
def __start_node(self, node_cfg: dict):
|
|
350
|
+
name = node_cfg.get("name", node_cfg.get("node_path"))
|
|
351
|
+
env = os.environ.copy()
|
|
352
|
+
env.update(node_cfg.get("env", {}) or {})
|
|
353
|
+
cwd = node_cfg.get("cwd") or None
|
|
354
|
+
cmd = self.__build_cmd(node_cfg)
|
|
355
|
+
|
|
356
|
+
proc = subprocess.Popen(
|
|
357
|
+
cmd,
|
|
358
|
+
cwd=cwd,
|
|
359
|
+
env=env,
|
|
360
|
+
stdout=subprocess.PIPE,
|
|
361
|
+
stderr=subprocess.PIPE,
|
|
362
|
+
preexec_fn=os.setsid,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
log_path = Path(
|
|
366
|
+
f"{self.__log_dir}/info/{name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
|
|
367
|
+
)
|
|
368
|
+
err_path = Path(
|
|
369
|
+
f"{self.__log_dir}/err/{name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.log"
|
|
370
|
+
)
|
|
371
|
+
log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
372
|
+
err_path.parent.mkdir(parents=True, exist_ok=True)
|
|
373
|
+
|
|
374
|
+
log_thread = threading.Thread(target=self.__stream_printer,
|
|
375
|
+
args=(name, proc.stdout, False, log_path,
|
|
376
|
+
self.__min_level),
|
|
377
|
+
daemon=True)
|
|
378
|
+
err_thread = threading.Thread(target=self.__stream_printer,
|
|
379
|
+
args=(name, proc.stderr, True, err_path,
|
|
380
|
+
self.__min_level),
|
|
381
|
+
daemon=True)
|
|
382
|
+
log_thread.start()
|
|
383
|
+
err_thread.start()
|
|
384
|
+
|
|
385
|
+
return {
|
|
386
|
+
"proc": proc,
|
|
387
|
+
"threads": (log_thread, err_thread),
|
|
388
|
+
"cfg": node_cfg
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
def __monitor_loop(self):
|
|
392
|
+
print(f"[launcher] Started {len(self.__node_cfgs)} nodes")
|
|
393
|
+
for node in self.__node_cfgs:
|
|
394
|
+
print(
|
|
395
|
+
f"[launcher] Starting {node.get('name', node.get('node_path'))}"
|
|
396
|
+
)
|
|
397
|
+
entry = self.__start_node(node)
|
|
398
|
+
with self.__state_lock:
|
|
399
|
+
self.__state[node.get("name", node.get("node_path"))] = entry
|
|
400
|
+
|
|
401
|
+
try:
|
|
402
|
+
while not self.__stop_event.is_set():
|
|
403
|
+
with self.__state_lock:
|
|
404
|
+
if not self.__state:
|
|
405
|
+
break
|
|
406
|
+
state_items = list(self.__state.items())
|
|
407
|
+
|
|
408
|
+
for name, entry in state_items:
|
|
409
|
+
proc = entry["proc"]
|
|
410
|
+
cfg_node = entry["cfg"]
|
|
411
|
+
|
|
412
|
+
if proc.poll() is not None:
|
|
413
|
+
print(
|
|
414
|
+
f"[launcher] Node {name} exited {proc.returncode}")
|
|
415
|
+
# Threads will stop automatically when stream ends
|
|
416
|
+
|
|
417
|
+
if cfg_node.get("respawn"):
|
|
418
|
+
delay = cfg_node.get("respawn_delay", 1)
|
|
419
|
+
print(
|
|
420
|
+
f"[launcher] Respawning {name} in {delay}s...")
|
|
421
|
+
time.sleep(delay)
|
|
422
|
+
new_entry = self.__start_node(cfg_node)
|
|
423
|
+
with self.__state_lock:
|
|
424
|
+
self.__state[name] = new_entry
|
|
425
|
+
else:
|
|
426
|
+
with self.__state_lock:
|
|
427
|
+
del self.__state[name]
|
|
428
|
+
|
|
429
|
+
time.sleep(0.5)
|
|
430
|
+
except KeyboardInterrupt:
|
|
431
|
+
pass
|
|
432
|
+
|
|
433
|
+
def __shutdown(self, signame):
|
|
434
|
+
if self.__shutdown_called:
|
|
435
|
+
return
|
|
436
|
+
self.__shutdown_called = True
|
|
437
|
+
|
|
438
|
+
print(f"[launcher] Got signal {signame}, shutting down children...")
|
|
439
|
+
with self.__state_lock:
|
|
440
|
+
state_items = list(self.__state.items())
|
|
441
|
+
|
|
442
|
+
for _, entry in state_items:
|
|
443
|
+
proc = entry["proc"]
|
|
444
|
+
try:
|
|
445
|
+
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
|
|
446
|
+
except Exception:
|
|
447
|
+
try:
|
|
448
|
+
proc.terminate()
|
|
449
|
+
except Exception:
|
|
450
|
+
pass
|
|
451
|
+
self.__stop_event.set()
|
|
452
|
+
self.__restore_terminal_attrs()
|
|
453
|
+
|
|
454
|
+
def run(self):
|
|
455
|
+
# Set up signal handlers
|
|
456
|
+
for s in (signal.SIGINT, signal.SIGTERM):
|
|
457
|
+
signal.signal(
|
|
458
|
+
s, lambda signum, frame: self.__shutdown(
|
|
459
|
+
signal.Signals(signum).name))
|
|
460
|
+
|
|
461
|
+
# Start monitor in a separate thread
|
|
462
|
+
monitor_thread = threading.Thread(target=self.__monitor_loop,
|
|
463
|
+
daemon=True)
|
|
464
|
+
monitor_thread.start()
|
|
465
|
+
|
|
466
|
+
# Wait for stop event or monitor thread to finish
|
|
467
|
+
while monitor_thread.is_alive() and not self.__stop_event.is_set():
|
|
468
|
+
self.__stop_event.wait(timeout=0.5)
|
|
469
|
+
|
|
470
|
+
# Give processes time to terminate gracefully
|
|
471
|
+
time.sleep(1.0)
|
|
472
|
+
|
|
473
|
+
# Force kill any remaining processes
|
|
474
|
+
with self.__state_lock:
|
|
475
|
+
state_items = list(self.__state.items())
|
|
476
|
+
|
|
477
|
+
for name, entry in state_items:
|
|
478
|
+
proc = entry["proc"]
|
|
479
|
+
if proc.poll() is None:
|
|
480
|
+
try:
|
|
481
|
+
os.killpg(os.getpgid(proc.pid), signal.SIGKILL)
|
|
482
|
+
except Exception:
|
|
483
|
+
try:
|
|
484
|
+
proc.kill()
|
|
485
|
+
except Exception:
|
|
486
|
+
pass
|
|
487
|
+
|
|
488
|
+
# Wait for monitor thread to finish
|
|
489
|
+
monitor_thread.join(timeout=0.5)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding:utf-8 -*-
|
|
3
|
+
################################################################
|
|
4
|
+
# Copyright 2025 Dong Zhaorui. All rights reserved.
|
|
5
|
+
# Author: Dong Zhaorui 847235539@qq.com
|
|
6
|
+
# Date : 2025-09-15
|
|
7
|
+
################################################################
|
|
8
|
+
|
|
9
|
+
from .mujoco_base import HexMujocoBase, HexMujocoClientBase, HexMujocoServerBase
|
|
10
|
+
from .archer_y6 import HexMujocoArcherY6, HexMujocoArcherY6Client, HexMujocoArcherY6Server
|
|
11
|
+
from .e3_desktop import HexMujocoE3Desktop, HexMujocoE3DesktopClient, HexMujocoE3DesktopServer
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
# base
|
|
15
|
+
"HexMujocoBase",
|
|
16
|
+
"HexMujocoClientBase",
|
|
17
|
+
"HexMujocoServerBase",
|
|
18
|
+
|
|
19
|
+
# archer_y6
|
|
20
|
+
"HexMujocoArcherY6",
|
|
21
|
+
"HexMujocoArcherY6Client",
|
|
22
|
+
"HexMujocoArcherY6Server",
|
|
23
|
+
|
|
24
|
+
# e3_desktop
|
|
25
|
+
"HexMujocoE3Desktop",
|
|
26
|
+
"HexMujocoE3DesktopClient",
|
|
27
|
+
"HexMujocoE3DesktopServer",
|
|
28
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding:utf-8 -*-
|
|
3
|
+
################################################################
|
|
4
|
+
# Copyright 2025 Dong Zhaorui. All rights reserved.
|
|
5
|
+
# Author: Dong Zhaorui 847235539@qq.com
|
|
6
|
+
# Date : 2025-09-15
|
|
7
|
+
################################################################
|
|
8
|
+
|
|
9
|
+
from .mujoco_archer_y6 import HexMujocoArcherY6
|
|
10
|
+
from .mujoco_archer_y6_cli import HexMujocoArcherY6Client
|
|
11
|
+
from .mujoco_archer_y6_srv import HexMujocoArcherY6Server
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"HexMujocoArcherY6",
|
|
15
|
+
"HexMujocoArcherY6Client",
|
|
16
|
+
"HexMujocoArcherY6Server",
|
|
17
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<mujocoinclude>
|
|
2
|
+
<mesh name="arm_base_link" file="arm_base_link.STL"/>
|
|
3
|
+
<mesh name="arm_link_1" file="arm_link_1.STL"/>
|
|
4
|
+
<mesh name="arm_link_2" file="arm_link_2.STL"/>
|
|
5
|
+
<mesh name="arm_link_3" file="arm_link_3.STL"/>
|
|
6
|
+
<mesh name="arm_link_4" file="arm_link_4.STL"/>
|
|
7
|
+
<mesh name="arm_link_5" file="arm_link_5.STL"/>
|
|
8
|
+
<mesh name="gripper_base_link" file="gripper_base_link.STL"/>
|
|
9
|
+
<mesh name="gripper_left_helper_link" file="gripper_left_helper_link.STL"/>
|
|
10
|
+
<mesh name="gripper_left_link_1" file="gripper_left_link_1.STL"/>
|
|
11
|
+
<mesh name="gripper_left_link_2" file="gripper_left_link_2.STL"/>
|
|
12
|
+
<mesh name="gripper_right_helper_link" file="gripper_right_helper_link.STL"/>
|
|
13
|
+
<mesh name="gripper_right_link_1" file="gripper_right_link_1.STL"/>
|
|
14
|
+
<mesh name="gripper_right_link_2" file="gripper_right_link_2.STL"/>
|
|
15
|
+
<mesh name="camera_link" file="camera_link.STL"/>
|
|
16
|
+
<mesh name="table_link" file="table_link.STL" scale="0.001 0.001 0.001"/>
|
|
17
|
+
</mujocoinclude>
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|