Skip to content

Commit eb96d60

Browse files
authored
Merge pull request #223 from JdeRobot/neutral-steps
Change visualization to tools
2 parents 49bc057 + 8f0a342 commit eb96d60

13 files changed

Lines changed: 460 additions & 450 deletions

manager/libs/launch_world_model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77

88
class ConfigurationModel(BaseModel):
9-
world: str
9+
type: str
1010
launch_file_path: str
1111

1212

manager/manager/docker_thread/docker_thread.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import subprocess
55
import os
66
import signal
7+
import sys
78

89

910
class DockerThread(threading.Thread):

manager/manager/launcher/launcher_console.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ class LauncherConsole(ILauncher):
1212
internal_port: int
1313
external_port: int
1414
running: bool = False
15+
acceptsMsgs: bool = False
1516
threads: List[Any] = []
1617
console_vnc: Any = Vnc_server()
1718

@@ -38,6 +39,15 @@ def run(self, config_file, callback):
3839

3940
self.running = True
4041

42+
def pause(self):
43+
pass
44+
45+
def unpause(self):
46+
pass
47+
48+
def reset(self):
49+
pass
50+
4151
def is_running(self):
4252
return self.running
4353

manager/manager/launcher/launcher_gazebo_view.py renamed to manager/manager/launcher/launcher_gazebo.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
from manager.manager.launcher.launcher_interface import ILauncher
23
from manager.manager.docker_thread.docker_thread import DockerThread
34
from manager.manager.vnc.vnc_server import Vnc_server
@@ -12,13 +13,26 @@
1213
from typing import List, Any
1314

1415

15-
class LauncherGazeboView(ILauncher):
16+
def call_service(service, service_type, request_data="{}"):
17+
command = f"ros2 service call {service} {service_type} '{request_data}'"
18+
subprocess.call(
19+
f"{command}",
20+
shell=True,
21+
stdout=sys.stdout,
22+
stderr=subprocess.STDOUT,
23+
bufsize=1024,
24+
universal_newlines=True,
25+
)
26+
27+
28+
class LauncherGazebo(ILauncher):
1629
display: str
1730
internal_port: int
1831
external_port: int
1932
height: int
2033
width: int
2134
running: bool = False
35+
acceptsMsgs: bool = False
2236
threads: List[Any] = []
2337
gz_vnc: Any = Vnc_server()
2438

@@ -51,6 +65,15 @@ def run(self, config_file, callback):
5165

5266
self.running = True
5367

68+
def pause(self):
69+
call_service("/pause_physics", "std_srvs/srv/Empty")
70+
71+
def unpause(self):
72+
call_service("/unpause_physics", "std_srvs/srv/Empty")
73+
74+
def reset(self):
75+
call_service("/reset_world", "std_srvs/srv/Empty")
76+
5477
def is_running(self):
5578
return self.running
5679

manager/manager/launcher/launcher_gzsim_view.py renamed to manager/manager/launcher/launcher_gzsim.py

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
from manager.manager.launcher.launcher_interface import ILauncher
23
from manager.manager.docker_thread.docker_thread import DockerThread
34
from manager.manager.vnc.vnc_server import Vnc_server
@@ -10,9 +11,48 @@
1011
import os
1112
import stat
1213
from typing import List, Any
14+
from manager.ram_logging.log_manager import LogManager
1315

1416

15-
class LauncherGzsimView(ILauncher):
17+
def call_gzservice(service, reqtype, reptype, timeout, req):
18+
command = f"gz service -s {service} --reqtype {reqtype} --reptype {reptype} --timeout {timeout} --req '{req}'"
19+
subprocess.call(
20+
f"{command}",
21+
shell=True,
22+
stdout=sys.stdout,
23+
stderr=subprocess.STDOUT,
24+
bufsize=1024,
25+
universal_newlines=True,
26+
)
27+
28+
29+
def call_service(service, service_type, request_data="{}"):
30+
command = f"ros2 service call {service} {service_type} '{request_data}'"
31+
subprocess.call(
32+
f"{command}",
33+
shell=True,
34+
stdout=sys.stdout,
35+
stderr=subprocess.STDOUT,
36+
bufsize=1024,
37+
universal_newlines=True,
38+
)
39+
40+
41+
def is_ros_service_available(service_name):
42+
try:
43+
result = subprocess.run(
44+
["ros2", "service", "list", "--include-hidden-services"],
45+
capture_output=True,
46+
text=True,
47+
check=True,
48+
)
49+
return service_name in result.stdout
50+
except subprocess.CalledProcessError as e:
51+
LogManager.logger.exception(f"Error checking service availability: {e}")
52+
return False
53+
54+
55+
class LauncherGzsim(ILauncher):
1656
display: str
1757
internal_port: int
1858
external_port: int
@@ -73,6 +113,41 @@ def terminate(self):
73113
def died(self):
74114
pass
75115

116+
def pause(self):
117+
call_gzservice(
118+
"$(gz service -l | grep '^/world/\w*/control$')",
119+
"gz.msgs.WorldControl",
120+
"gz.msgs.Boolean",
121+
"3000",
122+
"pause: true",
123+
)
124+
125+
def unpause(self):
126+
call_gzservice(
127+
"$(gz service -l | grep '^/world/\w*/control$')",
128+
"gz.msgs.WorldControl",
129+
"gz.msgs.Boolean",
130+
"3000",
131+
"pause: false",
132+
)
133+
134+
def reset(self):
135+
if is_ros_service_available("/drone0/platform/state_machine/_reset"):
136+
call_service(
137+
"/drone0/platform/state_machine/_reset",
138+
"std_srvs/srv/Trigger",
139+
"{}",
140+
)
141+
call_gzservice(
142+
"$(gz service -l | grep '^/world/\w*/control$')",
143+
"gz.msgs.WorldControl",
144+
"gz.msgs.Boolean",
145+
"3000",
146+
"reset: {all: true}",
147+
)
148+
if is_ros_service_available("/drone0/controller/_reset"):
149+
call_service("/drone0/controller/_reset", "std_srvs/srv/Trigger", "{}")
150+
76151
def get_dri_path(self):
77152
directory_path = "/dev/dri"
78153
dri_path = ""

manager/manager/launcher/launcher_robot.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@
6666

6767

6868
class LauncherRobot(BaseModel):
69-
world: str
69+
type: str
7070
launch_file_path: str
7171
module: str = ".".join(__name__.split(".")[:-1])
7272
ros_version: int = get_ros_version()
@@ -76,7 +76,7 @@ class LauncherRobot(BaseModel):
7676
def run(self, start_pose=None):
7777
if start_pose != None:
7878
self.start_pose = start_pose
79-
for module in worlds[self.world][str(self.ros_version)]:
79+
for module in worlds[self.type][str(self.ros_version)]:
8080
module["launch_file"] = self.launch_file_path
8181
launcher = self.launch_module(module)
8282
self.launchers.append(launcher)
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
from manager.libs.applications.compatibility.server import Server
2+
from manager.ram_logging.log_manager import LogManager
3+
from manager.comms.new_consumer import ManagerConsumer
4+
from typing import Optional
5+
from manager.libs.applications.compatibility.file_watchdog import FileWatchdog
6+
7+
8+
class LauncherStateMonitor:
9+
file: str
10+
consumer: ManagerConsumer
11+
running: bool = False
12+
acceptsMsgs: bool = True
13+
14+
def __init__(self, type, module, file, consumer):
15+
self.file = file
16+
self.consumer = consumer
17+
self.server = FileWatchdog("/tmp/tree_state", self.update)
18+
19+
def update(self, data):
20+
LogManager.logger.debug(f"Sending update to client")
21+
if self.consumer is not None:
22+
self.consumer.send_message({"update": data}, command="update")
23+
24+
def run(self, config_file, callback):
25+
self.server.start()
26+
self.running = True
27+
28+
def get_msg(self, data):
29+
self.server.send(data)
30+
31+
def is_running(self):
32+
return self.running
33+
34+
def terminate(self):
35+
self.server.stop()
36+
self.running = False
37+
38+
def pause(self):
39+
pass
40+
41+
def unpause(self):
42+
pass
43+
44+
def reset(self):
45+
pass
46+
47+
def died(self):
48+
pass
49+
50+
def from_config(cls, config):
51+
obj = cls(**config)
52+
return obj
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from manager.libs.process_utils import get_class, class_from_module
2+
from typing import Optional
3+
from pydantic import BaseModel
4+
5+
6+
from manager.libs.process_utils import get_class, class_from_module
7+
from manager.ram_logging.log_manager import LogManager
8+
from manager.manager.launcher.launcher_interface import ILauncher
9+
10+
tools = {
11+
"console": {
12+
"module": "console",
13+
"display": ":1",
14+
"external_port": 1108,
15+
"internal_port": 5901,
16+
},
17+
"gazebo": {
18+
"type": "module",
19+
"width": 1024,
20+
"height": 768,
21+
"module": "gazebo",
22+
"display": ":2",
23+
"external_port": 6080,
24+
"internal_port": 5900,
25+
},
26+
"gzsim": {
27+
"type": "module",
28+
"width": 1024,
29+
"height": 768,
30+
"module": "gzsim",
31+
"display": ":2",
32+
"external_port": 6080,
33+
"internal_port": 5900,
34+
},
35+
"web_gui": {
36+
"type": "module",
37+
"module": "web_gui",
38+
"internal_port": 2303,
39+
"consumer": None,
40+
},
41+
"state_monitor": {
42+
"type": "module",
43+
"module": "state_monitor",
44+
"file": "/tmp/tree_state",
45+
"consumer": None,
46+
},
47+
}
48+
49+
simulator = {
50+
"gazebo": {"tool": "gazebo"},
51+
"gz": {"tool": "gzsim"},
52+
}
53+
54+
55+
class LauncherTools(BaseModel):
56+
module: str = ".".join(__name__.split(".")[:-1])
57+
world_type: Optional[str] = None
58+
tools: list[str]
59+
tools_config: Optional[dict] = None
60+
launchers: Optional[ILauncher] = []
61+
62+
def run(self, consumer):
63+
for tool in self.tools:
64+
if tool == "simulator":
65+
tool = simulator[self.world_type]["tool"]
66+
module = tools[tool]
67+
launcher = self.launch_module(tool, module, consumer)
68+
self.launchers.append(launcher)
69+
70+
def terminate(self):
71+
LogManager.logger.info("Terminating tools launcher")
72+
for launcher in self.launchers:
73+
if launcher.is_running():
74+
launcher.terminate()
75+
self.launchers = []
76+
77+
def launch_module(self, name, configuration, consumer):
78+
def process_terminated(name, exit_code):
79+
LogManager.logger.info(
80+
f"LauncherEngine: {name} exited with code {exit_code}"
81+
)
82+
if self.terminated_callback is not None:
83+
self.terminated_callback(name, exit_code)
84+
85+
# Replace consumer
86+
if "consumer" in configuration:
87+
configuration["consumer"] = consumer
88+
89+
launcher_module_name = configuration["module"]
90+
launcher_module = f"{self.module}.launcher_{launcher_module_name}.Launcher{class_from_module(launcher_module_name)}"
91+
launcher_class = get_class(launcher_module)
92+
config = None
93+
if self.tools_config is not None and name in self.tools_config:
94+
config = self.tools_config[name]
95+
96+
launcher = launcher_class.from_config(launcher_class, configuration)
97+
launcher.run(config, process_terminated)
98+
return launcher
99+
100+
def pause(self):
101+
for launcher in self.launchers:
102+
launcher.pause()
103+
104+
def unpause(self):
105+
for launcher in self.launchers:
106+
launcher.unpause()
107+
108+
def reset(self):
109+
for launcher in self.launchers:
110+
launcher.reset()
111+
112+
def pass_msg(self, data):
113+
for launcher in self.launchers:
114+
if launcher.acceptsMsgs:
115+
launcher.get_msg(data)
116+
117+
def launch_command(self, configuration):
118+
pass
119+
120+
121+
class LauncherToolsException(Exception):
122+
def __init__(self, message):
123+
super(LauncherToolsException, self).__init__(message)

0 commit comments

Comments
 (0)