Spaces:
Configuration error
Configuration error
Commit
·
e11d6d3
1
Parent(s):
f40a19e
record mission log
Browse files- 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 |
-
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
957 |
<div style='font-family: "Courier New", monospace; color: #00ff00;'>
|
958 |
-
<b>STATUS:</b>
|
959 |
-
<b>
|
960 |
-
<b>
|
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
|