evangelosmeklis commited on
Commit
e11d6d3
·
1 Parent(s): f40a19e

record mission log

Browse files
Files changed (1) hide show
  1. drone/drone_chat.py +202 -7
drone/drone_chat.py CHANGED
@@ -9,9 +9,12 @@ import io
9
  import base64
10
  from .hf_model import HfApiModel
11
  import time
 
 
12
  # Import compatibility fix for collections.MutableMapping
13
  from . import compatibility_fix
14
  from . import drone_control # Import our new drone_control module
 
15
 
16
  # Set page config at module level - must be first Streamlit command
17
  st.set_page_config(
@@ -22,6 +25,73 @@ st.set_page_config(
22
  menu_items=None
23
  )
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  class DroneAssistant(CodeAgent):
26
  """Extension of CodeAgent for drone interactions"""
27
 
@@ -442,12 +512,19 @@ def connect_to_real_drone(connection_string: str = None) -> str:
442
  return "Error: Connection string is required. Examples: 'udp:127.0.0.1:14550' for simulation, '/dev/ttyACM0' for USB, or 'tcp:192.168.1.1:5760' for WiFi"
443
 
444
  try:
 
 
 
 
445
  success = drone_control.connect_drone(connection_string)
446
  if success:
447
  # Get and store current status
448
  location = drone_control.get_location()
449
  battery = drone_control.get_battery()
450
 
 
 
 
451
  # Format a nice response
452
  response = {
453
  "status": "Connected successfully",
@@ -456,8 +533,12 @@ def connect_to_real_drone(connection_string: str = None) -> str:
456
  }
457
  return str(response)
458
  else:
 
 
459
  return "Failed to connect to drone. Check connection string and ensure the drone is powered on."
460
  except Exception as e:
 
 
461
  return f"Error connecting to drone: {str(e)}"
462
 
463
  @tool
@@ -474,12 +555,23 @@ def drone_takeoff(altitude: float = None) -> str:
474
  return "Error: Altitude is required. Specify a safe takeoff altitude in meters."
475
 
476
  try:
 
 
 
 
 
 
 
 
477
  success = drone_control.takeoff(altitude)
478
  if success:
 
479
  return f"Takeoff successful! Reached target altitude of {altitude} meters."
480
  else:
 
481
  return "Takeoff failed. Make sure you are connected to the drone and in a safe takeoff area."
482
  except Exception as e:
 
483
  return f"Error during takeoff: {str(e)}"
484
 
485
  @tool
@@ -490,12 +582,19 @@ def drone_land() -> str:
490
  str: Status of the landing
491
  """
492
  try:
 
 
 
493
  success = drone_control.land()
494
  if success:
495
- return "Landing command sent successfully. The drone is descending to land."
 
 
496
  else:
 
497
  return "Landing command failed. Make sure you are connected to the drone."
498
  except Exception as e:
 
499
  return f"Error during landing: {str(e)}"
500
 
501
  @tool
@@ -506,12 +605,18 @@ def drone_return_home() -> str:
506
  str: Status of the return-to-home command
507
  """
508
  try:
 
 
 
509
  success = drone_control.return_home()
510
  if success:
 
511
  return "Return to home command sent successfully. The drone is returning to its launch point."
512
  else:
 
513
  return "Return to home command failed. Make sure you are connected to the drone."
514
  except Exception as e:
 
515
  return f"Error during return to home: {str(e)}"
516
 
517
  @tool
@@ -584,12 +689,49 @@ def execute_drone_mission(waypoints: List[Dict[str, float]] = None) -> str:
584
  return f"Error: Waypoint {i} is missing required keys. Each waypoint must have lat, lon, and alt."
585
 
586
  try:
 
 
 
 
 
 
 
 
 
 
587
  success = drone_control.execute_mission_plan(waypoints)
 
 
588
  if success:
589
- return f"Mission with {len(waypoints)} waypoints uploaded and started successfully."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  else:
 
591
  return "Failed to execute mission. Make sure you are connected to the drone."
592
  except Exception as e:
 
593
  return f"Error executing mission: {str(e)}"
594
 
595
  @tool
@@ -600,9 +742,15 @@ def disconnect_from_drone() -> str:
600
  str: Status of the disconnection
601
  """
602
  try:
 
 
 
603
  drone_control.disconnect_drone()
 
 
604
  return "Successfully disconnected from the drone."
605
  except Exception as e:
 
606
  return f"Error disconnecting from drone: {str(e)}"
607
 
608
  def create_qwen_model():
@@ -951,17 +1099,64 @@ def main():
951
 
952
  st.session_state['demo_data_loaded'] = True
953
 
954
- # Add mission status to sidebar
955
  st.sidebar.markdown("<h3 style='color: #00ff00; font-family: \"Courier New\", monospace;'>MISSION CONTROL</h3>", unsafe_allow_html=True)
956
- st.sidebar.markdown("""
 
 
 
 
 
 
 
 
 
 
957
  <div style='font-family: "Courier New", monospace; color: #00ff00;'>
958
- <b>STATUS:</b> OPERATIONAL<br>
959
- <b>BATTERY:</b> 87%<br>
960
- <b>ALTITUDE:</b> 153 M<br>
961
  <b>SIGNAL:</b> STRONG
962
  </div>
963
  """, unsafe_allow_html=True)
964
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
965
  st.sidebar.markdown("<hr style='border: 1px solid #00ff00; margin: 20px 0;'>", unsafe_allow_html=True)
966
 
967
  # Command reference
 
9
  import base64
10
  from .hf_model import HfApiModel
11
  import time
12
+ import datetime
13
+ import logging
14
  # Import compatibility fix for collections.MutableMapping
15
  from . import compatibility_fix
16
  from . import drone_control # Import our new drone_control module
17
+ import threading
18
 
19
  # Set page config at module level - must be first Streamlit command
20
  st.set_page_config(
 
25
  menu_items=None
26
  )
27
 
28
+ # Global mission status variables
29
+ if 'mission_in_progress' not in st.session_state:
30
+ st.session_state.mission_in_progress = False
31
+
32
+ if 'mission_status' not in st.session_state:
33
+ st.session_state.mission_status = "STANDBY"
34
+
35
+ if 'mission_phase' not in st.session_state:
36
+ st.session_state.mission_phase = ""
37
+
38
+ if 'interrupt_mission' not in st.session_state:
39
+ st.session_state.interrupt_mission = False
40
+
41
+ if 'mission_log' not in st.session_state:
42
+ st.session_state.mission_log = []
43
+
44
+ # Custom logging handler to capture drone_control logs
45
+ class MissionLogHandler(logging.Handler):
46
+ def emit(self, record):
47
+ if record.name == 'drone_control':
48
+ log_entry = f"[{datetime.datetime.now().strftime('%H:%M:%S')}] LOG: {record.getMessage()}"
49
+ st.session_state.mission_log.append(log_entry)
50
+ # Keep only the most recent logs
51
+ if len(st.session_state.mission_log) > 30:
52
+ st.session_state.mission_log = st.session_state.mission_log[-30:]
53
+
54
+ # Set up logger to capture drone_control logs
55
+ logger = logging.getLogger('drone_control')
56
+ mission_log_handler = MissionLogHandler()
57
+ logger.addHandler(mission_log_handler)
58
+
59
+ # Function to update mission status
60
+ def update_mission_status(status, phase=""):
61
+ # Get current time
62
+ timestamp = datetime.datetime.now().strftime("%H:%M:%S")
63
+
64
+ # Log the status change
65
+ log_entry = f"[{timestamp}] {status}: {phase}"
66
+ st.session_state.mission_log.append(log_entry)
67
+
68
+ # Keep only the most recent 30 log entries
69
+ if len(st.session_state.mission_log) > 30:
70
+ st.session_state.mission_log = st.session_state.mission_log[-30:]
71
+
72
+ # Update status
73
+ st.session_state.mission_status = status
74
+ st.session_state.mission_phase = phase
75
+
76
+ # No rerun here to avoid potential issues with recursive reruns
77
+
78
+ # Function to interrupt the mission
79
+ def interrupt_mission():
80
+ if st.session_state.mission_in_progress:
81
+ st.session_state.interrupt_mission = True
82
+ update_mission_status("INTERRUPTING", "Returning to base...")
83
+ # Call the return to home function
84
+ try:
85
+ drone_control.return_home()
86
+ time.sleep(2)
87
+ drone_control.disconnect_drone()
88
+ st.session_state.mission_in_progress = False
89
+ update_mission_status("ABORTED", "Mission aborted. Drone returned to base.")
90
+ except Exception as e:
91
+ update_mission_status("ERROR", f"Error during interrupt: {str(e)}")
92
+ else:
93
+ st.warning("No mission in progress to interrupt")
94
+
95
  class DroneAssistant(CodeAgent):
96
  """Extension of CodeAgent for drone interactions"""
97
 
 
512
  return "Error: Connection string is required. Examples: 'udp:127.0.0.1:14550' for simulation, '/dev/ttyACM0' for USB, or 'tcp:192.168.1.1:5760' for WiFi"
513
 
514
  try:
515
+ # Update mission status
516
+ st.session_state.mission_in_progress = True
517
+ update_mission_status("CONNECTING", f"Connecting to drone at {connection_string}")
518
+
519
  success = drone_control.connect_drone(connection_string)
520
  if success:
521
  # Get and store current status
522
  location = drone_control.get_location()
523
  battery = drone_control.get_battery()
524
 
525
+ # Update mission status
526
+ update_mission_status("CONNECTED", "Drone connected successfully")
527
+
528
  # Format a nice response
529
  response = {
530
  "status": "Connected successfully",
 
533
  }
534
  return str(response)
535
  else:
536
+ st.session_state.mission_in_progress = False
537
+ update_mission_status("ERROR", "Connection failed")
538
  return "Failed to connect to drone. Check connection string and ensure the drone is powered on."
539
  except Exception as e:
540
+ st.session_state.mission_in_progress = False
541
+ update_mission_status("ERROR", f"Connection error: {str(e)}")
542
  return f"Error connecting to drone: {str(e)}"
543
 
544
  @tool
 
555
  return "Error: Altitude is required. Specify a safe takeoff altitude in meters."
556
 
557
  try:
558
+ # Check if mission was interrupted
559
+ if st.session_state.interrupt_mission:
560
+ st.session_state.interrupt_mission = False
561
+ return "Takeoff aborted due to mission interrupt request"
562
+
563
+ # Update mission status
564
+ update_mission_status("TAKING OFF", f"Taking off to {altitude} meters")
565
+
566
  success = drone_control.takeoff(altitude)
567
  if success:
568
+ update_mission_status("AIRBORNE", f"Reached altitude of {altitude} meters")
569
  return f"Takeoff successful! Reached target altitude of {altitude} meters."
570
  else:
571
+ update_mission_status("ERROR", "Takeoff failed")
572
  return "Takeoff failed. Make sure you are connected to the drone and in a safe takeoff area."
573
  except Exception as e:
574
+ update_mission_status("ERROR", f"Takeoff error: {str(e)}")
575
  return f"Error during takeoff: {str(e)}"
576
 
577
  @tool
 
582
  str: Status of the landing
583
  """
584
  try:
585
+ # Update mission status
586
+ update_mission_status("LANDING", "Drone is landing")
587
+
588
  success = drone_control.land()
589
  if success:
590
+ update_mission_status("LANDED", "Drone has landed")
591
+ st.session_state.mission_in_progress = False
592
+ return "Landing command sent successfully. The drone has landed."
593
  else:
594
+ update_mission_status("ERROR", "Landing failed")
595
  return "Landing command failed. Make sure you are connected to the drone."
596
  except Exception as e:
597
+ update_mission_status("ERROR", f"Landing error: {str(e)}")
598
  return f"Error during landing: {str(e)}"
599
 
600
  @tool
 
605
  str: Status of the return-to-home command
606
  """
607
  try:
608
+ # Update mission status
609
+ update_mission_status("RETURNING", "Returning to launch point")
610
+
611
  success = drone_control.return_home()
612
  if success:
613
+ update_mission_status("RETURNING", "Drone is returning to launch point")
614
  return "Return to home command sent successfully. The drone is returning to its launch point."
615
  else:
616
+ update_mission_status("ERROR", "Return to home failed")
617
  return "Return to home command failed. Make sure you are connected to the drone."
618
  except Exception as e:
619
+ update_mission_status("ERROR", f"Return error: {str(e)}")
620
  return f"Error during return to home: {str(e)}"
621
 
622
  @tool
 
689
  return f"Error: Waypoint {i} is missing required keys. Each waypoint must have lat, lon, and alt."
690
 
691
  try:
692
+ # Update mission status
693
+ update_mission_status("MISSION", f"Starting mission with {len(waypoints)} waypoints")
694
+
695
+ # Check for mission interrupt before starting
696
+ if st.session_state.interrupt_mission:
697
+ st.session_state.interrupt_mission = False
698
+ update_mission_status("ABORTED", "Mission aborted before execution")
699
+ return "Mission aborted due to interrupt request"
700
+
701
+ # Execute mission with progress updates
702
  success = drone_control.execute_mission_plan(waypoints)
703
+
704
+ # Simulate mission progress (in a real implementation, you'd get actual progress from the drone)
705
  if success:
706
+ total_waypoints = len(waypoints)
707
+ for i in range(total_waypoints):
708
+ # Check for interrupt between waypoints
709
+ if st.session_state.interrupt_mission:
710
+ st.session_state.interrupt_mission = False
711
+ update_mission_status("INTERRUPTED", "Mission interrupted, returning to base")
712
+ drone_control.return_home()
713
+ time.sleep(2)
714
+ update_mission_status("RETURNED", "Drone returned to base after interrupt")
715
+ return f"Mission interrupted after waypoint {i+1}/{total_waypoints}. Drone returned to base."
716
+
717
+ # Update status for current waypoint
718
+ wp = waypoints[i]
719
+ update_mission_status(
720
+ "EXECUTING MISSION",
721
+ f"Flying to waypoint {i+1}/{total_waypoints}: lat={wp['lat']:.4f}, lon={wp['lon']:.4f}, alt={wp['alt']}m"
722
+ )
723
+
724
+ # Simulate time taken to reach waypoint
725
+ time.sleep(2)
726
+
727
+ # Mission completed successfully
728
+ update_mission_status("MISSION COMPLETE", "All waypoints reached")
729
+ return f"Mission with {len(waypoints)} waypoints completed successfully."
730
  else:
731
+ update_mission_status("ERROR", "Failed to execute mission")
732
  return "Failed to execute mission. Make sure you are connected to the drone."
733
  except Exception as e:
734
+ update_mission_status("ERROR", f"Mission error: {str(e)}")
735
  return f"Error executing mission: {str(e)}"
736
 
737
  @tool
 
742
  str: Status of the disconnection
743
  """
744
  try:
745
+ # Update mission status
746
+ update_mission_status("DISCONNECTING", "Disconnecting from drone")
747
+
748
  drone_control.disconnect_drone()
749
+ st.session_state.mission_in_progress = False
750
+ update_mission_status("STANDBY", "Disconnected from drone")
751
  return "Successfully disconnected from the drone."
752
  except Exception as e:
753
+ update_mission_status("ERROR", f"Disconnect error: {str(e)}")
754
  return f"Error disconnecting from drone: {str(e)}"
755
 
756
  def create_qwen_model():
 
1099
 
1100
  st.session_state['demo_data_loaded'] = True
1101
 
1102
+ # Add mission status section to sidebar with improved visibility
1103
  st.sidebar.markdown("<h3 style='color: #00ff00; font-family: \"Courier New\", monospace;'>MISSION CONTROL</h3>", unsafe_allow_html=True)
1104
+
1105
+ # Dynamic status display that changes color based on status
1106
+ status_color = "#00ff00" # Default green
1107
+ if st.session_state.mission_status == "ERROR":
1108
+ status_color = "#ff0000" # Red for errors
1109
+ elif st.session_state.mission_status in ["CONNECTING", "TAKING OFF", "LANDING", "RETURNING"]:
1110
+ status_color = "#ffff00" # Yellow for transitions
1111
+ elif st.session_state.mission_status in ["MISSION", "EXECUTING MISSION", "AIRBORNE"]:
1112
+ status_color = "#00ffff" # Cyan for active mission
1113
+
1114
+ st.sidebar.markdown(f"""
1115
  <div style='font-family: "Courier New", monospace; color: #00ff00;'>
1116
+ <b>STATUS:</b> <span style="color: {status_color}; font-weight: bold;">{st.session_state.mission_status}</span><br>
1117
+ <b>PHASE:</b> <span style="color: {status_color};">{st.session_state.mission_phase}</span><br>
1118
+ <b>ACTIVE:</b> {"YES" if st.session_state.mission_in_progress else "NO"}<br>
1119
  <b>SIGNAL:</b> STRONG
1120
  </div>
1121
  """, unsafe_allow_html=True)
1122
 
1123
+ # Add interrupt button if a mission is in progress
1124
+ if st.session_state.mission_in_progress:
1125
+ if st.sidebar.button("⚠️ ABORT MISSION",
1126
+ key="abort_button",
1127
+ help="Immediately abort the current mission and return the drone to base",
1128
+ type="primary"):
1129
+ interrupt_mission()
1130
+
1131
+ # Add mission log section
1132
+ with st.sidebar.expander("MISSION LOG", expanded=True):
1133
+ if not st.session_state.mission_log:
1134
+ st.write("No mission activity")
1135
+ else:
1136
+ log_html = "<div style='font-family: monospace; font-size: 11px; color: #00ff00; background-color: #111111; padding: 8px; border-radius: 5px; max-height: 300px; overflow-y: auto;'>"
1137
+ for entry in reversed(st.session_state.mission_log):
1138
+ if "ERROR" in entry:
1139
+ log_html += f"<div style='color: #ff0000;'>{entry}</div>"
1140
+ elif "LOG: Altitude:" in entry:
1141
+ # Special formatting for altitude logs
1142
+ altitude = entry.split("Altitude: ")[1]
1143
+ log_html += f"<div style='color: #88ff88;'>🛰️ {entry.split('] LOG: ')[0]}] ALT: {altitude}</div>"
1144
+ elif "LOG: Arming" in entry:
1145
+ log_html += f"<div style='color: #ffaa00;'>🔄 {entry}</div>"
1146
+ elif "LOG: Taking off" in entry:
1147
+ log_html += f"<div style='color: #ffff00;'>🚀 {entry}</div>"
1148
+ elif "LOG:" in entry:
1149
+ # Other log entries
1150
+ log_html += f"<div style='color: #aaaaff;'>{entry}</div>"
1151
+ elif any(status in entry for status in ["CONNECTING", "TAKING OFF", "LANDING", "RETURNING"]):
1152
+ log_html += f"<div style='color: #ffff00;'>{entry}</div>"
1153
+ elif any(status in entry for status in ["MISSION", "EXECUTING", "AIRBORNE"]):
1154
+ log_html += f"<div style='color: #00ffff;'>{entry}</div>"
1155
+ else:
1156
+ log_html += f"<div>{entry}</div>"
1157
+ log_html += "</div>"
1158
+ st.markdown(log_html, unsafe_allow_html=True)
1159
+
1160
  st.sidebar.markdown("<hr style='border: 1px solid #00ff00; margin: 20px 0;'>", unsafe_allow_html=True)
1161
 
1162
  # Command reference