add logging

This commit is contained in:
2025-10-23 19:36:08 +08:00
parent 62a4de2bad
commit 1013a4782b
2 changed files with 120 additions and 26 deletions

3
.gitignore vendored
View File

@@ -9,4 +9,5 @@ __pycache__/
*.pyd
*.swp
*.egg-info/
dist/
dist/
logs/

View File

@@ -14,12 +14,23 @@ from std_srvs.srv import Trigger
from nicegui import ui
from interfaces.msg import RobotWorkInfo
from starlette.staticfiles import StaticFiles
import datetime
import logging
class CtrlGuiNode(Node):
"""ROS2 node that publishes String messages to /robot_status."""
def __init__(self, node_name: str = 'ctrlgui_node'):
super().__init__(node_name)
# Initialize counters for monitoring
self.messages_sent = 0
self.messages_received = 0
self.rebuild_requests = 0
self.start_time = datetime.datetime.now()
# Setup logging to file
self._setup_file_logging()
# publisher on /robot_status
self.publisher_ = self.create_publisher(String, '/robot_status', 10)
self.run_trigger_ = self.create_client(Trigger, '/cerebrum/rebuild_now')
@@ -28,22 +39,82 @@ class CtrlGuiNode(Node):
self._sub_work_info = self.create_subscription(
RobotWorkInfo, '/robot_work_info', self._on_work_info, 10
)
self.get_logger().info('CtrlGuiNode initialized')
self.get_logger().info(f'Node started at: {self.start_time}')
def _setup_file_logging(self):
"""Setup logging to a file with timestamp in filename."""
# Create logs directory if it doesn't exist
log_dir = os.path.join(os.path.dirname(__file__), '..', 'logs')
os.makedirs(log_dir, exist_ok=True)
# Generate filename with timestamp
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
log_filename = os.path.join(log_dir, f"ctrlgui_{timestamp}.log")
# Create file handler
file_handler = logging.FileHandler(log_filename)
file_handler.setLevel(logging.INFO)
# Create formatter
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
# Create a dedicated logger for file output
self.file_logger = logging.getLogger(f"ctrlgui_file_{timestamp}")
self.file_logger.setLevel(logging.INFO)
self.file_logger.addHandler(file_handler)
self.log_filename = log_filename
self.get_logger().info('File logging setup complete.')
def _on_work_info(self, msg: RobotWorkInfo) -> None:
"""Callback for /robot_work_info subscription: store latest text."""
self.last_work_info = msg
self.messages_received += 1
# keep logs lightweight to avoid flooding
self.get_logger().debug(f"/robot_work_info: {self.last_work_info}")
self.get_logger().debug(f"/robot_work_info: {str(self.last_work_info)}")
# Log to file with message content
self.file_logger.info(f"Received message #{self.messages_received} from /robot_work_info")
self.file_logger.info(f"Message content - msg_id: {str(msg.msg_id)}, working_state: {str(msg.working_state)}, battery_capacity: {str(msg.battery_capacity)}, working_time: {str(msg.working_time)}, nav_state: {str(msg.nav_state)}, comm_quality: {str(msg.comm_quality)}, expt_completion_time: {str(msg.expt_completion_time)}, work_log: {str(msg.work_log)}, task: {str(list(msg.task))}, skill: {str(msg.skill)}, action_name: {str(msg.action_name)}, instance_params: {str(msg.instance_params)}, bt_node_status: {str(msg.bt_node_status)}, smacc_state: {str(msg.smacc_state)}")
# Log periodically to avoid spamming the logs
if self.messages_received % 10 == 0:
self.get_logger().info(f'Received {self.messages_received} messages from /robot_work_info')
def publish_info(self, text: str) -> None:
msg = String()
msg.data = text
self.publisher_.publish(msg)
self.get_logger().info(f'Published to /robot_status: {text}')
self.messages_sent += 1
self.get_logger().info(f'Published to /robot_status: {str(text)}')
self.get_logger().info(f'Total messages sent: {str(self.messages_sent)}')
# Log to file
self.file_logger.info(f"Published message #{self.messages_sent} to /robot_status: {str(text)}")
def rebuild_now(self) -> None:
self.rebuild_requests += 1
self.get_logger().info('Rebuild now')
self.get_logger().info(f'Total rebuild requests: {str(self.rebuild_requests)}')
self.run_trigger_.call_async(Trigger.Request())
# Log to file
self.file_logger.info(f"Rebuild requested. Total rebuild requests: {str(self.rebuild_requests)}")
def get_stats(self) -> dict:
"""Return node statistics for monitoring purposes."""
uptime = datetime.datetime.now() - self.start_time
return {
'uptime': str(uptime),
'messages_sent': self.messages_sent,
'messages_received': self.messages_received,
'rebuild_requests': self.rebuild_requests,
'start_time': str(self.start_time)
}
def build_ui(node: CtrlGuiNode) -> None:
"""Build the NiceGUI UI."""
@@ -65,7 +136,7 @@ def build_ui(node: CtrlGuiNode) -> None:
''')
# --- build UI ---
# ui.label('Robot Control GUI Node').classes('text-2xl')
# ui.label('HiveCore Robot Control Node').classes('text-2xl')
# message_input = ui.input('Message', value='Hello from NiceGUI')
# def send_message() -> None:
@@ -83,11 +154,18 @@ def build_ui(node: CtrlGuiNode) -> None:
try:
node.get_logger().info('Running rebuild_now...')
node.rebuild_now()
except Exception:
pass
except Exception as e:
node.get_logger().error(f'Failed to trigger rebuild: {str(e)}')
node.file_logger.error(f'Failed to trigger rebuild: {str(e)}')
ui.button('Rebuild', on_click=lambda: rebuild())
# Add statistics display
ui.separator()
ui.label('Node Statistics').classes('text-xl')
with ui.card().classes('w-full card-container'):
stats_label = ui.label()
# Display area for incoming /robot_work_info messages
ui.separator()
ui.label('Robot Work Information').classes('text-xl')
@@ -123,33 +201,45 @@ def build_ui(node: CtrlGuiNode) -> None:
def update_work_info():
msg = node.last_work_info
# Update statistics
stats = node.get_stats()
stats_text = f"""
Node Statistics:
- Uptime: {str(stats['uptime'])}
- Messages Sent: {str(stats['messages_sent'])}
- Messages Received: {str(stats['messages_received'])}
- Rebuild Requests: {str(stats['rebuild_requests'])}
- Start Time: {str(stats['start_time'])}
"""
stats_label.set_text(stats_text)
# Basic Information
msg_id_label.set_text(f'Message ID: {msg.msg_id}')
working_state_label.set_text(f'Working State: {msg.working_state}')
battery_capacity_label.set_text(f'Battery Capacity: {msg.battery_capacity}%')
working_time_label.set_text(f'Working Time: {msg.working_time}h')
nav_state_label.set_text(f'Navigation State: {msg.nav_state}')
comm_quality_label.set_text(f'Communication Quality: {msg.comm_quality}')
expt_completion_time_label.set_text(f'Expected Completion Time: {msg.expt_completion_time}')
work_log_label.set_text(f'Work Log: {msg.work_log}')
msg_id_label.set_text(f'Message ID: {str(msg.msg_id)}')
working_state_label.set_text(f'Working State: {str(msg.working_state)}')
battery_capacity_label.set_text(f'Battery Capacity: {str(msg.battery_capacity)}%')
working_time_label.set_text(f'Working Time: {str(msg.working_time)}h')
nav_state_label.set_text(f'Navigation State: {str(msg.nav_state)}')
comm_quality_label.set_text(f'Communication Quality: {str(msg.comm_quality)}')
expt_completion_time_label.set_text(f'Expected Completion Time: {str(msg.expt_completion_time)}')
work_log_label.set_text(f'Work Log: {str(msg.work_log)}')
# Task Information
tasks = list(msg.task)
if len(tasks) >= 3:
last_task_label.set_text(f'Last Task: {tasks[0]}')
next_task_label.set_text(f'Next Task: {tasks[1]}')
current_task_label.set_text(f'Current Task: {tasks[2]}')
last_task_label.set_text(f'Last Task: {str(tasks[0])}')
next_task_label.set_text(f'Next Task: {str(tasks[1])}')
current_task_label.set_text(f'Current Task: {str(tasks[2])}')
else:
last_task_label.set_text('Last Task: N/A')
next_task_label.set_text('Next Task: N/A')
current_task_label.set_text('Current Task: N/A')
# Skill Information
skill_label.set_text(f'Skill: {msg.skill}')
action_name_label.set_text(f'Action Name: {msg.action_name}')
instance_params_label.set_text(f'Instance Parameters: {msg.instance_params}')
bt_node_status_label.set_text(f'BT Node Status: {msg.bt_node_status}')
smacc_state_label.set_text(f'SMACC State: {msg.smacc_state}')
skill_label.set_text(f'Skill: {str(msg.skill)}')
action_name_label.set_text(f'Action Name: {str(msg.action_name)}')
instance_params_label.set_text(f'Instance Parameters: {str(msg.instance_params)}')
bt_node_status_label.set_text(f'BT Node Status: {str(msg.bt_node_status)}')
smacc_state_label.set_text(f'SMACC State: {str(msg.smacc_state)}')
ui.timer(0.5, update_work_info)
@@ -181,7 +271,10 @@ def main() -> None:
def shutdown() -> None:
try:
node.get_logger().info('Shutting down CtrlGuiNode...')
except Exception:
stats = node.get_stats()
node.get_logger().info(f'Final statistics: {str(stats)}')
node.file_logger.info(f'Node shutting down. Final statistics: {str(stats)}')
except Exception as e:
pass
try:
node.destroy_node()
@@ -211,6 +304,6 @@ if __name__ in {"__main__", "__mp_main__"}:
port = 8080
print(f'Starting NiceGUI on {host}:{port}...')
main()
ui.run(title='Robot Control GUI', host=host, port=port, reload=False, uvicorn_logging_level='info')
ui.run(title='HiveCore Robot Control', host=host, port=port, reload=False, uvicorn_logging_level='info')
else:
ui.run(main, title='Robot Control GUI')
ui.run(main, title='HiveCore Robot Control')