Skip to content

Commit

Permalink
merged tmux extension into airstack extension, cleaned up unused para…
Browse files Browse the repository at this point in the history
…meters in ascent omnigraph node, changed the IsaacSim login dialog popup to a console message, documented airstack extension (#144)
jfkeller authored Nov 13, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent bb9d5a8 commit 0efd64c
Showing 8 changed files with 626 additions and 40 deletions.
Binary file added docs/simulation/isaac_sim/ascent_node.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
21 changes: 21 additions & 0 deletions docs/simulation/isaac_sim/ascent_sitl_extension.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# AirLab AirStack Extension

The AirStack extension for IsaacSim does two main things. It creates an Ascent Omnigraph Node which runs the Ascent SITL and updates the position of a drone model in IsaacSim based on the SITL. It also creates a panel for listing, attaching to, and killing tmux sessions.

## Ascent OmniGraph Node

The Ascent OmniGraph node takes as input a domain id, node namespace and drone prim. It runs the Ascent SITL, mavproxy, and mavros and takes care of keeping the SITL time synced with IsaacSim's time. Mavros is run using the inputted domain id and node namespace. The drone prim's position is set based off of the position of the drone in the SITL. The drone prim doesn't do collision and will pass through objects in the IsaacSim world.

The way the SITL is synced with IsaacSim is by running the SITL in gdb with a breakpoint on the functin that advances the SITL time. Every time this function is called, our code is run by injecting a library using the LD_PRELOAD trick. Our code runs a client socket that talks to a server socket running in the AirStack IsaacSim extension which tells it how long to sleep based off the current SITL and IsaacSim time.

The Ascent OmniGraph node is shown below:

![Ascent OmniGraph Node](ascent_node.png)

## TMUX Panel

This is a panel for listing, attaching to, and killing any running TMUX sessions. The Ascent SITL, mavproxy, and mavros are run in a TMUX sesion, so this is mainly for debugging those and probably doesn't need to be interacted with by most users. A list of TMUX sessions is displayed in the panel. It doesn't auto refresh so you have to manually click the refresh button to display any changes in the list of sessions. For each session, there is an `Attach` button and a `Kill` button. The `Attach` button will bring up an `xterm` window with the TMUX session. The `Kill` button will kill the TMUX session.

The TMUX panel is shown below:

![TMUX Panel](tmux_panel.png)
Binary file added docs/simulation/isaac_sim/tmux_panel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions simulation/isaac-sim/docker/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -54,6 +54,7 @@ services:
- ~/docker/isaac-sim/data:/root/.local/share/ov/data:rw
- ~/docker/isaac-sim/documents:/root/Documents:rw
- ./user.config.json:/root/.local/share/ov/data/Kit/Isaac-Sim/4.1/user.config.json:rw
- ./ui.py:/isaac-sim/kit/exts/omni.kit.widget.nucleus_connector/omni/kit/widget/nucleus_connector/ui.py:rw
# developer stuff
- .dev:/root/.dev:rw # developer config
- .bashrc:/root/.bashrc:rw # bash config
456 changes: 456 additions & 0 deletions simulation/isaac-sim/docker/ui.py

Large diffs are not rendered by default.

3 changes: 1 addition & 2 deletions simulation/isaac-sim/docker/user_TEMPLATE.config.json
Original file line number Diff line number Diff line change
@@ -2702,7 +2702,6 @@
"exts": {
"enabled": {
"0": "airlab.airstack-1.19.1",
"1": "airlab.tmux_manager-0.1.0"
}
},
"window": {
@@ -2713,4 +2712,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -7,11 +7,13 @@
import socket
import struct
import time
import subprocess

import carb
import carb.dictionary
import omni
import omni.ext
import omni.ui as ui
import omni.graph.core as og
from omni.kit.app import get_app
from omni.isaac.core.world import World
@@ -219,6 +221,13 @@ def set_current_sim_time(self, current_sim_time):
self.current_sim_time = current_sim_time


def get_tmux_sessions():
text = subprocess.getoutput("tmux ls")

if ":" not in text:
return []
return list(map(lambda x: x.split(":")[0], text.split("\n")))

# ==============================================================================================================
class _PublicExtension(omni.ext.IExt):
"""Object that tracks the lifetime of the Python part of the extension loading"""
@@ -263,6 +272,93 @@ def on_startup(self):

# init physics callback
self.init_physics()


self.window = ui.Window("TMUX Manager", width=300, height=300)
self.window.deferred_dock_in("Property", ui.DockPolicy.DO_NOTHING)

#'''
with self.window.frame:
self.scroll = ui.ScrollingFrame(
horizontal_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON,
vertical_scrollbar_policy=ui.ScrollBarPolicy.SCROLLBAR_ALWAYS_ON,
)
with self.scroll:
with ui.VStack():
with ui.HStack(height=50):
ui.Label("TMUX Sessions:")
ui.Button("Refresh", clicked_fn=self.refresh_tmux_sessions)

ui.Spacer(height=5)
self.sessions_stack = ui.VStack()
#'''
self.sessions_dict = {}

self.refresh_tmux_sessions()

def refresh_tmux_sessions(self):
sessions = get_tmux_sessions()

# even when the hstack is destroyed it still takes up space so it is made not visible
# this probably means that the stack is still around and maybe if enough of them are created it will slow things down
# maybe a better way is to reuse them and if there are less than before make them not visible until more are needed
# for now it doesn't seem like it is impacting performance
for k in self.sessions_dict.keys():
self.sessions_dict[k]["label"].destroy()
self.sessions_dict[k]["attach_button"].destroy()
self.sessions_dict[k]["kill_button"].destroy()
self.sessions_dict[k]["hstack"].destroy()
self.sessions_dict[k]["hstack"].visible = False
self.sessions_dict = {}

for s in sessions:
if s in self.sessions_dict.keys():
continue
self.sessions_dict[s] = {}

with self.sessions_stack:
self.sessions_dict[s]["hstack"] = ui.HStack(height=50)
with self.sessions_dict[s]["hstack"]:
self.sessions_dict[s]["label"] = ui.Label(s)

def get_attach(session_name):
def attach():
# print('xterm -e "tmux a -t ' + session_name + '"')
subprocess.Popen(
'xterm -bg black -fg white -e "tmux a -t \\"'
+ session_name
+ '\\""',
shell=True,
)

return attach

def get_kill(session_name):
def kill():
# print('xterm -e "tmux kill-session -t ' + session_name + '"')
subprocess.Popen(
'xterm -e "tmux kill-session -t \\"'
+ session_name
+ '\\""',
shell=True,
)

return kill

self.sessions_dict[s]["attach_button"] = ui.Button(
"Attach", clicked_fn=get_attach(s)
)
self.sessions_dict[s]["kill_button"] = ui.Button(
"Kill", clicked_fn=get_kill(s)
)

keys_to_remove = []
for k in self.sessions_dict.keys():
if k not in sessions:
self.sessions_dict[k]["hstack"].destroy()
keys_to_remove.append(k)
for k in keys_to_remove:
del self.sessions_dict[k]

def init_physics(self, was_playing=False):
self.world = World()
@@ -287,6 +383,8 @@ def timeline_callback(self, event):
self.init_physics()
elif event.type == int(omni.timeline.TimelineEventType.STOP):
self.current_sim_time = 0.

self.refresh_tmux_sessions()

def physics_callback(self, sim_time_delta):
self.current_sim_time += sim_time_delta
@@ -295,6 +393,7 @@ def physics_callback(self, sim_time_delta):

def on_shutdown(self):
print('SHUTDOWN')
self.refresh_tmux_sessions()
self.__stage_subscription = None
self.__opt_in_setting_sub = None
self.world.remove_physics_callback('airstack_physics_callback')
Original file line number Diff line number Diff line change
@@ -135,30 +135,30 @@ class OgnAscentNodeDatabase(og.Database):
False,
"",
),
(
"inputs:deltaSimulationTime",
"double",
0,
"Simulation Delta Time",
"Description.",
{},
True,
0.0,
False,
"",
),
(
"inputs:deltaSystemTime",
"double",
0,
"System Delta Time",
"Description.",
{},
True,
0.0,
False,
"",
),
# (
# "inputs:deltaSimulationTime",
# "double",
# 0,
# "Simulation Delta Time",
# "Description.",
# {},
# True,
# 0.0,
# False,
# "",
# ),
# (
# "inputs:deltaSystemTime",
# "double",
# 0,
# "System Delta Time",
# "Description.",
# {},
# True,
# 0.0,
# False,
# "",
# ),
(
"inputs:dronePrim",
"target",
@@ -234,8 +234,8 @@ def _populate_role_data(cls):
class ValuesForInputs(og.DynamicAttributeAccess):
LOCAL_PROPERTY_NAMES = {
"execIn",
"deltaSimulationTime",
"deltaSystemTime",
#"deltaSimulationTime",
#"deltaSystemTime",
"dronePrim",
"domain_id",
"nodeNamespace",
@@ -259,8 +259,8 @@ def __init__(
print("init 3")
self._batchedReadAttributes = [
self._attributes.execIn,
self._attributes.deltaSimulationTime,
self._attributes.deltaSystemTime,
#self._attributes.deltaSimulationTime,
#self._attributes.deltaSystemTime,
self._attributes.dronePrim,
self._attributes.domain_id,
self._attributes.nodeNamespace,
@@ -278,7 +278,7 @@ def execIn(self):
def execIn(self, value):
print("execIn 2")
self._batchedReadValues[0] = value

'''
@property
def deltaSimulationTime(self):
return self._batchedReadValues[1]
@@ -294,30 +294,30 @@ def deltaSystemTime(self):
@deltaSystemTime.setter
def deltaSystemTime(self, value):
self._batchedReadValues[2] = value

'''
@property
def dronePrim(self):
return self._batchedReadValues[3]
return self._batchedReadValues[1]

@dronePrim.setter
def dronePrim(self, value):
self._batchedReadValues[3] = value
self._batchedReadValues[1] = value

@property
def domain_id(self):
return self._batchedReadValues[4]
return self._batchedReadValues[2]

@domain_id.setter
def domain_id(self, value):
self._batchedReadValues[4] = value
self._batchedReadValues[2] = value

@property
def nodeNamespace(self):
return self._batchedReadValues[5]
return self._batchedReadValues[3]

@nodeNamespace.setter
def nodeNamespace(self, value):
self._batchedReadValues[5] = value
self._batchedReadValues[3] = value

def __getattr__(self, item: str):
if item in self.LOCAL_PROPERTY_NAMES:
@@ -454,20 +454,30 @@ def get_node_type():

@staticmethod
def compute(context, node):
# print('compute 1')
#print('compute 1')
def database_valid():
return True

try:
#print('1')
per_node_data = OgnAscentNodeDatabase.PER_NODE_DATA[node.node_id()]
#print('2')
db = per_node_data.get("_db")
#print('3')
if db is None:
#print('4')
db = OgnAscentNodeDatabase(node)
#print('5')
per_node_data["_db"] = db
#print('6')
if not database_valid():
#print('7')
per_node_data["_db"] = None
#print('8')
return False
except:
except Exception as e:
#print('9', e)
#traceback.print_exc()
db = OgnAscentNodeDatabase(node)
# print('input test', dir(db.inputs))
# print('delta sim time', db.inputs.deltaSimulationTime)

0 comments on commit 0efd64c

Please sign in to comment.