diff --git a/.gitignore b/.gitignore index d9c4027..ca71ae3 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,5 @@ __pycache__/ *.pyd *.swp *.egg-info/ -dist/ \ No newline at end of file +dist/ +logs/ \ No newline at end of file diff --git a/ctrlgui/ctrlgui_node.py b/ctrlgui/ctrlgui_node.py index da924b0..76f4b5b 100644 --- a/ctrlgui/ctrlgui_node.py +++ b/ctrlgui/ctrlgui_node.py @@ -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') \ No newline at end of file + ui.run(main, title='HiveCore Robot Control') \ No newline at end of file