Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 2 additions & 28 deletions autonav_ws/src/autonav_commander/src/commander.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@
"autonav_commander", # Ourselves
"UnityEndpoint", # Unity TCP Connection
]
REQUIRED_NODES = [
"autonav_sd_display"
]
USER_HOME = os.path.expanduser("~")
PRESETS_DIR = os.path.join(USER_HOME, ".autonav", "presets")

Expand Down Expand Up @@ -107,29 +104,6 @@ def preset_save_callback(self, msg: String):
f.write(msg.data)
self.log(f"Set active preset to {msg.data}", LogLevel.INFO)

def can_initialize_node(self, node_name: str) -> bool:
# Check if all required nodes are present
missing_node = None
for required_node in REQUIRED_NODES:
if required_node not in self.alive_nodes:
missing_node = required_node
break

# If missing_node is not None and node_name is not the missing node, return False
if missing_node is not None and node_name != missing_node:
return False

# If the node is in the ignore list, return False
if node_name in IGNORE_NODES:
return False

# If the node is already alive, return False
if node_name in self.alive_nodes:
return False

# If all checks passed, return True
return True

def on_tick(self):
network = self.get_node_names()

Expand All @@ -142,7 +116,7 @@ def on_tick(self):
# Add all nodes that have not been seen
new_nodes = []
for node in network:
if self.can_initialize_node(node):
if node not in self.alive_nodes and node not in IGNORE_NODES:
new_nodes.append(node)
self.alive_nodes.append(node)

Expand All @@ -152,7 +126,7 @@ def on_tick(self):
node_cfg = self.preset.get(node)
if node_cfg is not None:
self.log(f"node cfg: {node_cfg}", LogLevel.DEBUG)
self._broadcast_config(node, node_cfg)
# self._broadcast_config(node, node_cfg)

time.sleep(0.3)

Expand Down
199 changes: 175 additions & 24 deletions autonav_ws/src/autonav_display_node/src/display.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#!/usr/bin/env -S python3
# TODO DO NOT COMMIT THIS FILE!! IT SHOULD BE REVERTED ONCE YOU GET WS WORKING
import asyncio
import json
import threading
import time
import os
import glob

import cv2
import cv_bridge
Expand All @@ -23,34 +24,34 @@ class Topics(Enum):# todo refactor to json file being read in..
BROADCAST = "/autonav/shared/autonav_display_broadcast"
SYSTEM_STATE = "/autonav/shared/system"
DEVICE_STATE = "/autonav/shared/device"
LOG = "/autonav/shared/log"#todo confirm implementation
LOG = "/autonav/shared/log"

# IMU Data
IMU = "/autonav/imu"
AUTONAV_GPS = "/autonav/gps"
MOTOR_INPUT = "/autonav/motor_input"
POSITION = "/autonav/position"
CONTROLLER_INPUT = '/autonav/controller_input'#todo confirm implementation
CONTROLLER_INPUT = '/autonav/controller_input'

# Motor and System Feedback
MOTOR_FEEDBACK = "/autonav/motor_feedback"
NUC_STATISTICS = "/autonav/statistics"#todo implement in index
ULTRASONICS = "/autonav/ultrasonic"#todo implement in index
CONBUS_DATA = "/autonav/conbus/data"#todo confirm implementation
CONBUS_INSTRUCTION = "/autonav/conbus/instruction"#todo confirm implementation
NUC_STATISTICS = "/autonav/statistics"
ULTRASONICS = "/autonav/ultrasonic"
CONBUS_DATA = "/autonav/conbus/data"
CONBUS_INSTRUCTION = "/autonav/conbus/instruction"

# Aliases for backward compatibility
CONBUS = "/autonav/conbus/data"#todo confirm implementation
SAFETY_LIGHTS = "/autonav/safety_lights"#todo implement in index
PERFORMANCE = "/autonav/performance"#todo implement in index
CONBUS = "/autonav/conbus/data"
SAFETY_LIGHTS = "/autonav/safety_lights"
PERFORMANCE = "/autonav/performance"

# PID and Motor Statistics
LINEAR_PID_STATISTICS = "/autonav/linear_pid_statistics"#todo confirm implementation
ANGULAR_PID_STATISTICS = "/autonav/angular_pid_statistics"#todo confirm implementation
MOTOR_STATISTICS_FRONT = "/autonav/motor_statistics_front_motors"#todo confirm implementation
MOTOR_STATISTICS_BACK = "/autonav/motor_statistics_back_motors"#todo confirm implementation
CAN_STATS = "/autonav/can_stats"#todo confirm implementation
ZERO_ENCODERS = "/autonav/zero_encoders"#todo confirm implementation
LINEAR_PID_STATISTICS = "/autonav/linear_pid_statistics"
ANGULAR_PID_STATISTICS = "/autonav/angular_pid_statistics"
MOTOR_STATISTICS_FRONT = "/autonav/motor_statistics_front_motors"
MOTOR_STATISTICS_BACK = "/autonav/motor_statistics_back_motors"
CAN_STATS = "/autonav/can_stats"
ZERO_ENCODERS = "/autonav/zero_encoders"

# Raw camera
RAW_LEFT = "/autonav/camera/left"
Expand All @@ -60,17 +61,17 @@ class Topics(Enum):# todo refactor to json file being read in..

# Other Camera Nodes
COMBINED_IMAGE = "/autonav/vision/combined/filtered"
FEELERS = "/autonav/feelers/debug" # todo implement in index
FEELERS = "/autonav/feelers/debug"

# Configuration
CONFIGURATION_BROADCAST = "/autonav/shared/config/requests"#todo confirm implementation
CONFIGURATION_UPDATE = "/autonav/shared/config/updates" #todo implement in index
CONFIG_PRESTS_LOAD = "/autonav/presets/load"#todo implement in index
CONFIG_PRESTS_SAVE = "/autonav/presets/save"#todo implement in index
CONFIGURATION_BROADCAST = "/autonav/shared/config/requests"
CONFIGURATION_UPDATE = "/autonav/shared/config/updates"
CONFIG_PRESTS_LOAD = "/autonav/presets/load"
CONFIG_PRESTS_SAVE = "/autonav/presets/save"

# Others
PLAYBACK = "/autonav/autonav_playback" # TODO see how to feed this data in
AUDIBLE_FEEDBACK = '/autonav/audible_feedback' # todo implement!
PLAYBACK = "/autonav/autonav_playback" #
AUDIBLE_FEEDBACK = '/autonav/audible_feedback'


async_loop = asyncio.new_event_loop()
Expand Down Expand Up @@ -466,9 +467,64 @@ async def handle_ws_message(self, obj, uid):
"mobility": self.mobility
}
}), uid)
elif obj.get("op") == "get_log_files":

log_files = self.get_log_files()
self.push_old(json.dumps({
"op": "get_log_files_callback",
"log_files": log_files
}), uid)
elif obj.get("op") == "get_log_file_content":

log_file_path = obj.get("log_file_path")
if log_file_path:
content = self.get_log_file_content(log_file_path)
self.push_old(json.dumps({
"op": "get_log_file_content_callback",
"log_file_path": log_file_path,
"content": content
}), uid)
elif obj.get("op") == "get_configuration":
device = obj.get("device")
if device:
# -m ConfigurationBroadcast msg 2 req config
msg = ConfigurationBroadcast()
msg.device = device
msg.opcode = 4 # Get configuration

#publisher
self.configuration_broadcast_p = self.create_publisher(
ConfigurationBroadcast,
Topics.CONFIGURATION_BROADCAST.value,
20
)
self.configuration_broadcast_p.publish(msg)

# response send
self.push_old(json.dumps({
"op": "get_configuration_response",
"device": device,
"status": "requested"
}), uid)
elif obj.get("op") == "configuration":
device = obj.get("device")
json_data = obj.get("json")
if device and json_data:
# pusher to robot
msg = ConfigurationUpdate()
msg.device = device
msg.json = json_data if isinstance(json_data, str) else json.dumps(json_data)


self.configuration_update_p = self.create_publisher(
ConfigurationUpdate,
Topics.CONFIGURATION_UPDATE.value,
20
)
self.configuration_update_p.publish(msg)
# elif obj.get("op") == "set_system_state": todo fix setting sys state?
# self.set_system_total_state(int(obj["state"]), int(obj["mode"]), bool(obj["mobility"]))
# TODO: add configuration, conbus, preset ops to ws msg
# TODO: add conbus, preset ops to ws msg


#todo add new callbacks
Expand Down Expand Up @@ -537,11 +593,106 @@ def zeroEncodersCallback(self, msg: ZeroEncoders):
def configurationBroadcastCallback(self, msg: ConfigurationBroadcast):
self.push(Topics.CONFIGURATION_BROADCAST.value, msg)

# If this is a response to a get_configuration request (opcode 4),
# send the configuration back to all clients
if msg.opcode == 4 and hasattr(msg, 'json') and msg.json:
try:
config_data = json.loads(msg.json)
self.push_old(json.dumps({
"op": "get_configuration_response",
"device": msg.device,
"config": config_data
}))
except json.JSONDecodeError:
self.get_logger().error(f"Failed to parse configuration JSON: {msg.json}")
except Exception as e:
self.get_logger().error(f"Error processing configuration broadcast: {str(e)}")

# Cameras
def cameraCallback(self, msg: CompressedImage, key: str):
topic = getattr(Topics, f"RAW_{key.upper()}" if key in ['left', 'right', 'front', 'back'] else f"FEELERS").value
self.push_image(topic, msg)

def get_log_files(self):
log_files = []
home_dir = os.path.expanduser("~")
log_base_dir = os.path.join(home_dir, ".autonav", "logs")


if not os.path.exists(log_base_dir):
return log_files


for mode in ["manual", "autonomous"]:
mode_dir = os.path.join(log_base_dir, mode)
if os.path.exists(mode_dir):

timestamp_dirs = glob.glob(os.path.join(mode_dir, "*"))
for timestamp_dir in timestamp_dirs:
if os.path.isdir(timestamp_dir):

timestamp = os.path.basename(timestamp_dir)


log_file = os.path.join(timestamp_dir, "output.suslog")
if os.path.exists(log_file):
log_files.append({
"mode": mode,
"timestamp": timestamp,
"path": log_file,
"size": os.path.getsize(log_file)
})


zip_files = glob.glob(os.path.join(log_base_dir, "*.zip"))
for zip_file in zip_files:
filename = os.path.basename(zip_file)

parts = filename.split("_", 1)
if len(parts) == 2:
mode = parts[0]
timestamp = parts[1].replace(".zip", "")
log_files.append({
"mode": mode,
"timestamp": timestamp,
"path": zip_file,
"size": os.path.getsize(zip_file),
"is_zip": True
})


log_files.sort(key=lambda x: x["timestamp"], reverse=True)

return log_files

def get_log_file_content(self, log_file_path):
"""
Get the content of a log file
Returns the content as a string or an error message
"""
try:
if not os.path.exists(log_file_path):
return {"error": "File not found"}


if log_file_path.endswith(".zip"):
return {"error": "Zip files cannot be viewed directly"}


with open(log_file_path, "r") as f:
content = f.read()


try:
parsed_content = json.loads(content)
return {"content": parsed_content}
except json.JSONDecodeError:

return {"content": content, "is_plain_text": True}

except Exception as e:
return {"error": str(e)}

def init(self):
self.set_device_state(AutonavDeviceState.OPERATING)

Expand Down
Loading