Skip to content

Commit d04269e

Browse files
committed
Set SimBasicDrone as default ID so then it can be loaded by other instance of world client
1 parent a6d8c94 commit d04269e

File tree

3 files changed

+75
-57
lines changed

3 files changed

+75
-57
lines changed
Lines changed: 60 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,117 @@
11
"""
2-
Copyright (C) Microsoft Corporation.
2+
Copyright (C) Microsoft Corporation.
33
Copyright (C) 2025 IAMAI Consulting Corp.
44
MIT License. All rights reserved.
55
6-
Demonstrates flying a quadrotor drone with camera sensors.
6+
Secondary controller+viewer: flies the drone AND subscribes to camera topics to show images.
77
"""
88

99
import asyncio
10+
import signal
1011

1112
from projectairsim import ProjectAirSimClient, Drone, World
1213
from projectairsim.utils import projectairsim_log
1314
from projectairsim.image_utils import ImageDisplay
1415

15-
# Async main function to wrap async drone commands
16+
1617
async def main():
17-
# Create a Project AirSim client
1818
client = ProjectAirSimClient()
19-
20-
# Initialize an ImageDisplay object to display camera sub-windows
2119
image_display = ImageDisplay()
2220

21+
# Graceful shutdown with Ctrl+C
22+
stop_event = asyncio.Event()
2323
try:
24-
# Connect to simulation environment
25-
client.connect()
26-
27-
# Create a World object to interact with the sim world and load a scene
28-
world = World(client, "scene_two_drones.jsonc", delay_after_load_sec=2, actual_load=False)
29-
30-
# Create a Drone object to interact with a drone in the loaded sim world
31-
drone = Drone(client, world, "Drone2")
32-
33-
# ------------------------------------------------------------------------------
24+
signal.signal(signal.SIGINT, lambda *_: stop_event.set())
25+
except Exception:
26+
pass
3427

28+
try:
29+
# 1) Connect to the running simulation (primary already loaded the scene)
30+
client.connect()
3531

36-
# Set the drone to be ready to fly
32+
# 2) Attach to current world without loading a new scene
33+
world = World(client)
34+
35+
# 3) Attach to the existing drone
36+
drone = Drone(client, world, "Drone1")
37+
38+
# ----------------- SUBSCRIPTIONS (viewer) -----------------
39+
# Downward RGB
40+
rgb_win = "RGB-Image (Secondary)"
41+
image_display.add_image(rgb_win, subwin_idx=0)
42+
try:
43+
down_rgb_topic = drone.sensors["DownCamera"]["scene_camera"]
44+
except Exception:
45+
down_rgb_topic = "/Sim/SceneBasicDrone/robots/Drone1/sensors/DownCamera/scene_camera"
46+
client.subscribe(down_rgb_topic, lambda _, msg: image_display.receive(msg, rgb_win))
47+
48+
# Depth
49+
depth_win = "Depth-Image (Secondary)"
50+
image_display.add_image(depth_win, subwin_idx=2)
51+
try:
52+
down_depth_topic = drone.sensors["DownCamera"]["depth_camera"]
53+
except Exception:
54+
down_depth_topic = "/Sim/SceneBasicDrone/robots/Drone1/sensors/DownCamera/depth_camera"
55+
client.subscribe(down_depth_topic, lambda _, msg: image_display.receive(msg, depth_win))
56+
57+
# Chase cam
58+
chase_win = "ChaseCam (Secondary)"
59+
image_display.add_chase_cam(chase_win)
60+
try:
61+
chase_topic = drone.sensors["Chase"]["scene_camera"]
62+
except Exception:
63+
chase_topic = "/Sim/SceneBasicDrone/robots/Drone1/sensors/Chase/scene_camera"
64+
client.subscribe(chase_topic, lambda _, msg: image_display.receive(msg, chase_win))
65+
66+
# Start viewer windows
67+
image_display.start()
68+
69+
# ----------------- FLIGHT SEQUENCE (control) -----------------
70+
# Keep the same behavior you had: enable, arm, takeoff, move up/down, land
3771
drone.enable_api_control()
3872
drone.arm()
3973

40-
# ------------------------------------------------------------------------------
41-
4274
projectairsim_log().info("takeoff_async: starting")
43-
takeoff_task = (
44-
await drone.takeoff_async()
45-
) # schedule an async task to start the command
46-
47-
# Example 1: Wait on the result of async operation using 'await' keyword
75+
takeoff_task = await drone.takeoff_async()
4876
await takeoff_task
4977
projectairsim_log().info("takeoff_async: completed")
5078

51-
# ------------------------------------------------------------------------------
52-
53-
# Command the drone to move up in NED coordinate system at 1 m/s for 4 seconds
79+
# Move up 1 m/s for 4 s
5480
move_up_task = await drone.move_by_velocity_async(
5581
v_north=0.0, v_east=0.0, v_down=-1.0, duration=4.0
5682
)
5783
projectairsim_log().info("Move-Up invoked")
58-
5984
await move_up_task
6085
projectairsim_log().info("Move-Up completed")
6186

62-
# ------------------------------------------------------------------------------
63-
64-
# Command the Drone to move down in NED coordinate system at 1 m/s for 4 seconds
87+
# Move down 1 m/s for 4 s
6588
move_down_task = await drone.move_by_velocity_async(
6689
v_north=0.0, v_east=0.0, v_down=1.0, duration=4.0
67-
) # schedule an async task to start the command
90+
)
6891
projectairsim_log().info("Move-Down invoked")
69-
70-
# Example 2: Wait for move_down_task to complete before continuing
7192
while not move_down_task.done():
7293
await asyncio.sleep(0.005)
7394
projectairsim_log().info("Move-Down completed")
7495

75-
# ------------------------------------------------------------------------------
76-
7796
projectairsim_log().info("land_async: starting")
7897
land_task = await drone.land_async()
7998
await land_task
8099
projectairsim_log().info("land_async: completed")
81100

82-
# ------------------------------------------------------------------------------
101+
# Keep windows open until Ctrl+C (optional)
102+
projectairsim_log().info("Viewer running. Press Ctrl+C to quit.")
103+
await stop_event.wait()
83104

84-
# Shut down the drone
105+
# Disarm/disable after viewing (optional)
85106
drone.disarm()
86107
drone.disable_api_control()
87108

88-
# ------------------------------------------------------------------------------
89-
90-
# logs exception on the console
91109
except Exception as err:
92110
projectairsim_log().error(f"Exception occurred: {err}", exc_info=True)
93-
94111
finally:
95-
# Always disconnect from the simulation environment to allow next connection
96112
client.disconnect()
97-
98113
image_display.stop()
99114

100115

101116
if __name__ == "__main__":
102-
asyncio.run(main()) # Runner for async main function
117+
asyncio.run(main())

client/python/projectairsim/src/projectairsim/drone.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ def set_sensor_topics(self, world: World):
7474
scene_config_data = world.get_configuration()
7575
data = None
7676

77+
if scene_config_data is None:
78+
return
79+
7780
for actor in scene_config_data["actors"]:
7881
if actor["name"] == self.name:
7982
data = actor["robot-config"]

client/python/projectairsim/src/projectairsim/world.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ def __init__(
4242
delay_after_load_sec: int = 0,
4343
sim_config_path: str = "sim_config/",
4444
sim_instance_idx: int = -1,
45-
actual_load: bool = True,
4645
):
4746
"""ProjectAirSim World Interface.
4847
@@ -71,7 +70,7 @@ def __init__(
7170
self.robot_config_paths = config_paths[1]
7271
self.envactor_config_paths = config_paths[2]
7372
self.envobject_config_paths = config_paths[3]
74-
self.load_scene(config_dict, delay_after_load_sec=delay_after_load_sec, actual_load = actual_load)
73+
self.load_scene(config_dict, delay_after_load_sec=delay_after_load_sec)
7574
random.seed()
7675
self.import_ned_trajectory(
7776
"null_trajectory", [0, 1], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0], [0, 0]
@@ -83,6 +82,12 @@ def get_configuration(self) -> Dict:
8382
Returns:
8483
Dict: the configuration
8584
"""
85+
# self.sim_config is not setted when World is created without parameters
86+
if self.sim_config is None:
87+
projectairsim_log().info(
88+
f"World '{self.parent_topic}' was created without parameters"
89+
)
90+
return None
8691
return self.sim_config
8792

8893
def get_sim_clock_type(self) -> str:
@@ -1007,7 +1012,7 @@ def set_sun_position_from_date_time(
10071012
return status
10081013

10091014
def load_scene(
1010-
self, scene_config_dict: Dict, delay_after_load_sec: float = 0, actual_load: bool = True
1015+
self, scene_config_dict: Dict, delay_after_load_sec: float = 0
10111016
) -> str:
10121017
"""(Re)loads the sim scene config to the sim server
10131018
@@ -1021,9 +1026,7 @@ def load_scene(
10211026

10221027
# Force sim to pause on start if there are objects to spawn
10231028
clock_is_steppable = scene_config_dict["clock"]["type"] == "steppable"
1024-
pause_on_start_by_user = False
1025-
if scene_config_dict["clock"].get("pause-on-start") is not None:
1026-
pause_on_start_by_user = scene_config_dict["clock"]["pause-on-start"]
1029+
pause_on_start_by_user = scene_config_dict["clock"].get("pause-on-start", False)
10271030

10281031
if scene_config_dict.get("spawn-objects") is not None:
10291032
if clock_is_steppable:
@@ -1044,12 +1047,9 @@ def load_scene(
10441047
}
10451048
self.sim_config = scene_config_dict
10461049

1047-
if(actual_load):
1048-
self.client.unsubscribe_all()
1049-
scene_id = self.client.request(load_scene_req)
1050-
self.client.get_topic_info() # get new scene's list of registered topic info
1051-
else:
1052-
scene_id = scene_config_dict["id"]
1050+
self.client.unsubscribe_all()
1051+
scene_id = self.client.request(load_scene_req)
1052+
self.client.get_topic_info() # get new scene's list of registered topic info
10531053

10541054
time.sleep(delay_after_load_sec)
10551055
self.parent_topic = f"/Sim/{scene_id}"

0 commit comments

Comments
 (0)