yjhjkcl 8 months ago
parent fc6329bbaf
commit 90544a2efb

@ -0,0 +1,32 @@
# 应用环境
ENVIRONMENT=development # development, testing, production
# 日志配置
LOG_LEVEL=INFO # DEBUG, INFO, WARNING, ERROR, CRITICAL
# Web服务器配置
WEB_HOST=0.0.0.0
WEB_PORT=8080
# 通信服务器配置
COMM_HOST=0.0.0.0
COMM_PORT=5000
# 无人机连接配置
DRONE_CONNECTION_STRING=udpin:localhost:14550
# 基地位置
BASE_LATITUDE=39.9
BASE_LONGITUDE=116.3
# 飞行参数
DEFAULT_ALTITUDE=10.0
DEFAULT_AIRSPEED=3.0
# 安全设置
ENABLE_GEO_FENCE=True
MAX_DISTANCE_FROM_BASE=5000.0
MIN_BATTERY_LEVEL=30.0
# 其他配置
ENABLE_SIMULATION=True

@ -0,0 +1,73 @@
# 智能战场医疗后送系统
基于无人机的智能战场医疗后送系统,用于快速、高效地运送医疗资源和伤员。
## 功能特点
- 实时伤员位置报告
- 医疗资源运送
- 无人机状态监控
- 简单易用的操作界面
## 系统要求
- Python 3.8+
- 无人机硬件支持MAVLink协议
- GPS模块
- 通信模块4G/5G或Wi-Fi
## 安装说明
1. 克隆项目
```bash
git clone [项目地址]
```
2. 安装依赖
```bash
pip install -r requirements.txt
```
3. 配置环境变量
```bash
cp .env.example .env
# 编辑.env文件填入必要的配置信息
```
4. 运行系统
```bash
python main.py
```
## 项目结构
```
├── main.py # 主程序入口
├── requirements.txt # 项目依赖
├── config/ # 配置文件目录
├── src/ # 源代码目录
│ ├── drone/ # 无人机控制模块
│ ├── communication/ # 通信模块
│ ├── positioning/ # 定位模块
│ └── ui/ # 用户界面模块
└── tests/ # 测试文件目录
```
## 使用说明
1. 启动系统后通过Web界面登录
2. 在地图上标记伤员位置
3. 选择需要运送的医疗资源
4. 发送任务指令给无人机
5. 实时监控无人机状态和任务进度
## 注意事项
- 使用前请确保无人机电量充足
- 确保GPS信号良好
- 遵守相关法律法规
- 注意战场环境安全
## 许可证
MIT License

@ -0,0 +1,110 @@
import os
import logging
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# 基础配置
class Config:
"""基础配置类"""
# 应用配置
APP_NAME = "智能战场医疗后送系统"
VERSION = "1.0.0"
# 日志配置
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper()
LOG_FORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
# Web服务器配置
WEB_HOST = os.getenv("WEB_HOST", "0.0.0.0")
WEB_PORT = int(os.getenv("WEB_PORT", "8080"))
# 通信服务器配置
COMM_HOST = os.getenv("COMM_HOST", "0.0.0.0")
COMM_PORT = int(os.getenv("COMM_PORT", "5000"))
# 无人机连接配置
DRONE_CONNECTION_STRING = os.getenv("DRONE_CONNECTION_STRING", "udpin:localhost:14550")
# 基地位置(纬度、经度)
BASE_LATITUDE = float(os.getenv("BASE_LATITUDE", "39.9"))
BASE_LONGITUDE = float(os.getenv("BASE_LONGITUDE", "116.3"))
# 飞行参数
DEFAULT_ALTITUDE = float(os.getenv("DEFAULT_ALTITUDE", "10.0")) # 默认飞行高度(米)
DEFAULT_AIRSPEED = float(os.getenv("DEFAULT_AIRSPEED", "3.0")) # 默认空速(米/秒)
# 安全设置
ENABLE_GEO_FENCE = os.getenv("ENABLE_GEO_FENCE", "True").lower() == "true"
MAX_DISTANCE_FROM_BASE = float(os.getenv("MAX_DISTANCE_FROM_BASE", "5000.0")) # 最大飞行距离(米)
MIN_BATTERY_LEVEL = float(os.getenv("MIN_BATTERY_LEVEL", "30.0")) # 最低电量百分比
# 其他配置
ENABLE_SIMULATION = os.getenv("ENABLE_SIMULATION", "True").lower() == "true"
@staticmethod
def get_log_level():
"""获取日志级别"""
level_map = {
"DEBUG": logging.DEBUG,
"INFO": logging.INFO,
"WARNING": logging.WARNING,
"ERROR": logging.ERROR,
"CRITICAL": logging.CRITICAL
}
return level_map.get(Config.LOG_LEVEL, logging.INFO)
@staticmethod
def setup_logging():
"""设置日志"""
logging.basicConfig(
level=Config.get_log_level(),
format=Config.LOG_FORMAT
)
# 降低第三方库的日志级别
logging.getLogger("werkzeug").setLevel(logging.WARNING)
logging.getLogger("socketio").setLevel(logging.WARNING)
logging.getLogger("engineio").setLevel(logging.WARNING)
logging.getLogger("geventwebsocket").setLevel(logging.WARNING)
logging.info(f"日志系统已初始化: 级别={Config.LOG_LEVEL}")
class DevelopmentConfig(Config):
"""开发环境配置"""
ENABLE_SIMULATION = True
LOG_LEVEL = "DEBUG"
class ProductionConfig(Config):
"""生产环境配置"""
ENABLE_SIMULATION = False
LOG_LEVEL = "INFO"
class TestingConfig(Config):
"""测试环境配置"""
ENABLE_SIMULATION = True
LOG_LEVEL = "DEBUG"
WEB_PORT = 8081
COMM_PORT = 5001
# 根据环境变量选择配置
def get_config():
"""获取当前环境的配置"""
env = os.getenv("ENVIRONMENT", "development").lower()
if env == "production":
return ProductionConfig
elif env == "testing":
return TestingConfig
else:
return DevelopmentConfig
# 导出当前配置
current_config = get_config()

@ -0,0 +1,89 @@
#!/bin/bash
# 确保目录结构存在
mkdir -p src/drone/
mkdir -p src/communication/
mkdir -p src/positioning/
mkdir -p src/ui/templates/
mkdir -p src/ui/static/css/
mkdir -p src/ui/static/js/
mkdir -p src/ui/static/img/
mkdir -p config/
mkdir -p tests/
# 创建空的__init__.py文件确保模块可以被导入
touch src/__init__.py
touch src/drone/__init__.py
touch src/communication/__init__.py
touch src/positioning/__init__.py
touch src/ui/__init__.py
touch config/__init__.py
touch tests/__init__.py
# 创建无人机和伤员图标
echo "创建示例图标..."
# 无人机图标
cat > src/ui/static/img/drone.png << EOF
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAA
AXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAA
DsMAAA7DAcdvqGQAAAKDSURBVFhHxZdPSBRhGMZnZ/9o/9xd
d1ddT0GHUiGDoEMQhEGHOhR0COoQdKhLHToEXYIuQYc8BR3q
VNChQ9Ch6BAEHaJDRIeIVNBW27Vd/7XrzrwzO9O8M9/MfLM7
s+sXPPDyvO/7vd+8O7PrjrFGnMP+vgZ0dnZmzs/Pb0aj0QBG
e97QJyqRSOB8Po8WFxcPAZcYehD0H9GRy+WyiAcpM4BvuKn/
qCiKKkVRphgacCrYcRFPOHWQxpPJ5HWn/lZQFYkHQYWC0mJU
wkk8CAqgKvHyIMchCDGqkuPQoEQQJMehAWLUiSAYZJbfb5TN
Zvc45zrQCfN+NiC0iG4RoQUQIYi7xMuDHIcgxKhKjkODEkGQ
HIcGiFF1iECQWbqDZmZmWs+0w2P9osBJtBvQCQdxXRA4wE5g
NZvNcpzjnMsTlHQA12q1E4CfAF47OTnp5TgvJCh6jO5Fh4eH
Wcj7gPfAHx1APB7/heMxdH9lZeUaJz00CrgYCCGb8/PzF7PZ
7KKqqsfGzMuHVAECyePj428qlconY+blQwbQBXg3nU4/xgXk
G6/CILtgjGgX8AGOBYyJ14YxoxeZqrPwkN2CfUQDbI2yGRGx
lI+FdAuwM1qtVhPbIxmPx28ZMxdEAXbGW1tbK81m81MqlRox
Zi6ILoB3wpOTk0cAj8Hr7e3t58bMBa/nQAdxPB/GA/nU1dV1
wZhZIP8GaJBMJge2t7efxWKxH9PT0/nBwcFOjnPCi4EQYYLZ
WVb8FZIpE0D89MTERO/Ozs55o+TlQ7YLsJPvPEbrOA7X6/XL
uCZrxk4P2QDojPgf0wlgEvB+aWlpbWho6J/REqA6Rv8B0U+G
YRp/6oQAAAAASUVORK5CYII=
EOF
# 伤员图标
cat > src/ui/static/img/casualty.png << EOF
iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAA
AXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAA
DsMAAA7DAcdvqGQAAAKDSURBVFhHxZdPSBRhGMZnZ/9o/9xd
d1ddT0GHUiGDoEMQhEGHOhR0COoQdKhLHToEXYIuQYc8BR3q
VNChQ9Ch6BAEHaJDRIeIVNBW27Vd/7XrzrwzO9O8M9/MfLM7
s+sXPPDyvO/7vd+8O7PrjrFGnMP+vgZ0dnZmzs/Pb0aj0QBG
e97QJyqRSOB8Po8WFxcPAZcYehD0H9GRy+WyiAcpM4BvuKn/
qCiKKkVRphgacCrYcRFPOHWQxpPJ5HWn/lZQFYkHQYWC0mJU
wkk8CAqgKvHyIMchCDGqkuPQoEQQJMehAWLUiSAYZJbfb5TN
Zvc45zrQCfN+NiC0iG4RoQUQIYi7xMuDHIcgxKhKjkODEkGQ
HIcGiFF1iECQWbqDZmZmWs+0w2P9osBJtBvQCQdxXRA4wE5g
NZvNcpzjnMsTlHQA12q1E4CfAF47OTnp5TgvJCh6jO5Fh4eH
Wcj7gPfAHx1APB7/heMxdH9lZeUaJz00CrgYCCGb8/PzF7PZ
7KKqqsfGzMuHVAECyePj428qlconY+blQwbQBXg3nU4/xgXk
G6/CILtgjGgX8AGOBYyJ14YxoxeZqrPwkN2CfUQDbI2yGRGx
lI+FdAuwM1qtVhPbIxmPx28ZMxdEAXbGW1tbK81m81MqlRox
Zi6ILoB3wpOTk0cAj8Hr7e3t58bMBa/nQAdxPB/GA/nU1dV1
wZhZIP8GaJBMJge2t7efxWKxH9PT0/nBwcFOjnPCi4EQYYLZ
WVb8FZIpE0D89MTERO/Ozs55o+TlQ7YLsJPvPEbrOA7X6/XL
uCZrxk4P2QDojPgf0wlgEvB+aWlpbWho6J/REqA6Rv8B0U+G
YRp/6oQAAAAASUVORK5CYII=
EOF
# 创建环境变量文件
cp .env.example .env
# 创建一个简单的运行脚本
cat > run.sh << EOF
#!/bin/bash
python simulation.py "\$@"
EOF
chmod +x run.sh
echo "项目初始化完成!"
echo "运行以下命令安装依赖:"
echo " pip install -r requirements.txt"
echo "运行系统:"
echo " ./run.sh"

@ -0,0 +1,311 @@
import logging
import os
import signal
import sys
import threading
from config.config import current_config as config
from src.drone.drone_controller import DroneController
from src.drone.mission_planner import MissionPlanner, Mission, MissionType
from src.drone.medical_supplies import MedicalSupplyManager
from src.communication.communication_manager import CommunicationManager
from src.positioning.position_manager import PositionManager
from src.ui.web_interface import WebInterface
# 设置日志
config.setup_logging()
logger = logging.getLogger(__name__)
class MedicalEvacuationSystem:
def __init__(self):
"""初始化医疗后送系统"""
logger.info(f"初始化{config.APP_NAME} v{config.VERSION}")
# 初始化各个模块
self.drone_controller = DroneController(config.DRONE_CONNECTION_STRING)
self.medical_supply_manager = MedicalSupplyManager()
self.position_manager = PositionManager()
self.communication_manager = CommunicationManager(
host=config.COMM_HOST,
port=config.COMM_PORT
)
self.web_interface = WebInterface(
host=config.WEB_HOST,
port=config.WEB_PORT
)
self.mission_planner = None
# 注册处理器
self._register_handlers()
def _register_handlers(self):
"""注册各种处理器"""
# 注册Web界面处理器
self.web_interface.set_casualty_handler(self._handle_casualty_request)
self.web_interface.set_supply_handler(self._handle_supply_request)
self.web_interface.set_drone_status_handler(self._handle_drone_status_request)
# 注册通信管理器处理器
self.communication_manager.set_casualty_handler(self._handle_casualty_request)
self.communication_manager.set_supply_handler(self._handle_supply_request)
def _handle_casualty_request(self, data):
"""处理伤员请求"""
try:
casualty_id = data.get('casualty_id')
latitude = data.get('latitude')
longitude = data.get('longitude')
if casualty_id and latitude is not None and longitude is not None:
# 添加或更新伤员位置
self.position_manager.add_casualty_position(casualty_id, latitude, longitude)
# 广播伤员位置更新
self.web_interface.broadcast_casualty_update(data)
logger.info(f"处理伤员请求: ID={casualty_id}, 位置=({latitude}, {longitude})")
return True
else:
logger.warning("无效的伤员请求数据")
return False
except Exception as e:
logger.error(f"处理伤员请求失败: {str(e)}")
return False
def _handle_supply_request(self, data):
"""处理物资请求"""
try:
supply_id = data.get('type')
quantity = data.get('quantity', 1)
target_id = data.get('target_id')
if not supply_id or not target_id:
logger.warning("无效的物资请求数据")
return False
# 获取伤员位置
casualty_position = self.position_manager.get_casualty_position(target_id)
if not casualty_position:
logger.warning(f"未找到伤员位置: {target_id}")
return False
# 创建物资请求
request_id = self.medical_supply_manager.create_supply_request(
target_id, supply_id, quantity
)
if not request_id:
logger.warning("创建物资请求失败")
return False
# 创建物资运送任务
supply = self.medical_supply_manager.get_supply(supply_id)
mission = Mission(
mission_id=f"mission_{request_id}",
mission_type=MissionType.MEDICAL_SUPPLY,
target_location=(casualty_position['latitude'], casualty_position['longitude']),
altitude=config.DEFAULT_ALTITUDE,
payload={
'request_id': request_id,
'supply_id': supply_id,
'supply_name': supply.name if supply else "未知物资",
'quantity': quantity,
'target_id': target_id
}
)
# 添加任务到任务规划器
self.mission_planner.add_mission(mission)
# 更新物资请求状态
self.medical_supply_manager.update_request_status(request_id, 'in_progress')
# 广播物资状态更新
self._broadcast_supply_status()
logger.info(f"处理物资请求: ID={request_id}, 物资={supply_id}, 目标={target_id}")
return True
except Exception as e:
logger.error(f"处理物资请求失败: {str(e)}")
return False
def _handle_drone_status_request(self):
"""处理无人机状态请求"""
try:
# 获取当前任务
current_mission = self.mission_planner.get_current_mission()
mission_status = "执行任务" if current_mission else "待机"
# 获取无人机位置
if self.drone_controller.vehicle:
location = self.drone_controller.vehicle.location.global_relative_frame
battery = self.drone_controller.vehicle.battery.level if self.drone_controller.vehicle.battery else 0
else:
location = None
battery = 0
# 构建状态数据
if location:
status_data = {
'status': mission_status,
'battery': battery,
'latitude': location.lat,
'longitude': location.lon,
'altitude': location.alt if hasattr(location, 'alt') else 0,
'current_mission': current_mission.to_dict() if current_mission else None
}
else:
# 模拟数据
status_data = {
'status': '未连接',
'battery': 0,
'latitude': config.BASE_LATITUDE,
'longitude': config.BASE_LONGITUDE,
'altitude': 0,
'current_mission': None
}
# 广播无人机状态
self.web_interface.broadcast_drone_update(status_data)
return status_data
except Exception as e:
logger.error(f"处理无人机状态请求失败: {str(e)}")
return {
'status': '错误',
'battery': 0,
'latitude': config.BASE_LATITUDE,
'longitude': config.BASE_LONGITUDE,
'altitude': 0,
'error': str(e)
}
def _broadcast_supply_status(self):
"""广播物资状态"""
try:
supplies = []
for supply_id, supply in self.medical_supply_manager.get_all_supplies().items():
supplies.append({
'id': supply_id,
'name': supply.name,
'description': supply.description,
'quantity': supply.quantity
})
self.web_interface.broadcast_medical_supply_update({
'supplies': supplies
})
except Exception as e:
logger.error(f"广播物资状态失败: {str(e)}")
def _broadcast_mission_status(self):
"""广播任务状态"""
try:
current_mission = self.mission_planner.get_current_mission()
mission_history = self.mission_planner.get_mission_history()
self.web_interface.socketio.emit('mission_status_updated', {
'currentMission': current_mission.to_dict() if current_mission else None,
'missionHistory': mission_history
})
except Exception as e:
logger.error(f"广播任务状态失败: {str(e)}")
def start(self):
"""启动系统"""
try:
logger.info("启动系统...")
# 连接无人机
if not config.ENABLE_SIMULATION:
if not self.drone_controller.connect():
logger.error("无法连接到无人机,请检查连接设置")
return False
else:
logger.info("系统运行在模拟模式,跳过无人机连接")
# 初始化任务规划器
self.mission_planner = MissionPlanner(self.drone_controller)
self.mission_planner.start()
# 启动通信服务器
communication_thread = threading.Thread(
target=self.communication_manager.start_server,
daemon=True
)
communication_thread.start()
# 启动任务状态广播定时器
def broadcast_status():
while True:
self._handle_drone_status_request()
self._broadcast_mission_status()
self._broadcast_supply_status()
threading.Event().wait(5) # 每5秒广播一次
status_thread = threading.Thread(target=broadcast_status, daemon=True)
status_thread.start()
# 启动Web界面
self.web_interface.start()
return True
except Exception as e:
logger.error(f"启动系统失败: {str(e)}")
return False
def stop(self):
"""停止系统"""
try:
logger.info("正在停止系统...")
# 停止任务规划器
if self.mission_planner:
self.mission_planner.stop()
# 关闭无人机连接
if not config.ENABLE_SIMULATION:
self.drone_controller.close()
# 关闭通信服务器
self.communication_manager.close()
logger.info("系统已停止")
except Exception as e:
logger.error(f"停止系统失败: {str(e)}")
def signal_handler(sig, frame):
"""信号处理函数"""
logger.info("接收到停止信号,正在关闭系统...")
system.stop()
sys.exit(0)
def main():
"""主程序入口"""
global system
# 注册信号处理器
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# 创建系统实例
system = MedicalEvacuationSystem()
# 启动系统
if system.start():
logger.info("系统启动成功")
else:
logger.error("系统启动失败")
system.stop()
return 1
return 0
if __name__ == "__main__":
system = None
exit_code = main()
sys.exit(exit_code)

@ -0,0 +1,9 @@
pymavlink==2.4.35
dronekit==2.9.2
pyserial==3.5
flask==2.3.3
flask-socketio==5.3.6
python-dotenv==1.0.0
geopy==2.4.1
numpy==1.24.3
pandas==2.0.3

@ -0,0 +1,194 @@
#!/usr/bin/env python3
import os
import sys
import time
import logging
import random
import threading
import signal
import argparse
from config.config import current_config as config
# 确保环境变量设置为启用模拟
os.environ['ENABLE_SIMULATION'] = 'True'
# 设置日志
config.setup_logging()
logger = logging.getLogger(__name__)
class DroneSimulator:
"""无人机模拟器"""
def __init__(self, port=14550):
"""初始化模拟器"""
self.port = port
self.running = False
self.thread = None
self.latitude = config.BASE_LATITUDE
self.longitude = config.BASE_LONGITUDE
self.altitude = 0.0
self.battery = 100.0
self.armed = False
self.mode = "GUIDED"
def start(self):
"""启动模拟器"""
if self.running:
return
self.running = True
self.thread = threading.Thread(target=self._simulation_loop, daemon=True)
self.thread.start()
logger.info(f"模拟器已启动在端口 {self.port}")
def stop(self):
"""停止模拟器"""
if not self.running:
return
self.running = False
if self.thread:
self.thread.join(timeout=2)
logger.info("模拟器已停止")
def _simulation_loop(self):
"""模拟循环"""
while self.running:
# 模拟电池消耗
if self.armed and self.battery > 0:
self.battery -= 0.01
if self.battery < 0:
self.battery = 0
# 模拟位置变化
if self.armed and self.mode == "GUIDED" and self.altitude > 0:
# 随机漂移
self.latitude += random.uniform(-0.00001, 0.00001)
self.longitude += random.uniform(-0.00001, 0.00001)
time.sleep(0.1)
def arm(self):
"""解锁无人机"""
self.armed = True
logger.info("模拟无人机已解锁")
def disarm(self):
"""锁定无人机"""
self.armed = False
logger.info("模拟无人机已锁定")
def set_mode(self, mode):
"""设置模式"""
self.mode = mode
logger.info(f"模拟无人机模式已设置为 {mode}")
def takeoff(self, altitude):
"""起飞"""
self.arm()
self.altitude = altitude
logger.info(f"模拟无人机起飞到高度 {altitude}m")
def land(self):
"""降落"""
self.altitude = 0
self.disarm()
logger.info("模拟无人机已降落")
def goto(self, lat, lon, alt):
"""飞往指定位置"""
logger.info(f"模拟无人机飞往 ({lat}, {lon}, {alt})")
# 模拟飞行时间
distance = ((lat - self.latitude) ** 2 + (lon - self.longitude) ** 2) ** 0.5 * 111000
speed = 5.0 # 假设速度为5m/s
flight_time = distance / speed
# 模拟飞行过程
start_lat = self.latitude
start_lon = self.longitude
start_alt = self.altitude
for i in range(10):
progress = (i + 1) / 10
self.latitude = start_lat + (lat - start_lat) * progress
self.longitude = start_lon + (lon - start_lon) * progress
self.altitude = start_alt + (alt - start_alt) * progress
time.sleep(flight_time / 10)
self.latitude = lat
self.longitude = lon
self.altitude = alt
logger.info(f"模拟无人机已到达 ({lat}, {lon}, {alt})")
def setup_simulation():
"""设置模拟环境"""
# 启动模拟器
simulator = DroneSimulator()
simulator.start()
# 创建SITL连接字符串和覆盖环境变量
os.environ['DRONE_CONNECTION_STRING'] = f'udpin:localhost:{simulator.port}'
return simulator
def run_simulation():
"""运行模拟"""
# 设置参数解析
parser = argparse.ArgumentParser(description='智能战场医疗后送系统模拟器')
parser.add_argument('--web-port', type=int, default=8080, help='Web服务端口')
parser.add_argument('--comm-port', type=int, default=5000, help='通信服务端口')
parser.add_argument('--drone-port', type=int, default=14550, help='无人机模拟器端口')
args = parser.parse_args()
# 设置环境变量
os.environ['WEB_PORT'] = str(args.web_port)
os.environ['COMM_PORT'] = str(args.comm_port)
# 启动模拟器
simulator = DroneSimulator(port=args.drone_port)
simulator.start()
# 导入主程序模块
from main import MedicalEvacuationSystem
# 创建系统实例
system = MedicalEvacuationSystem()
# 注册信号处理
def signal_handler(sig, frame):
logger.info("接收到停止信号,正在关闭模拟器...")
system.stop()
simulator.stop()
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# 启动系统
try:
if system.start():
logger.info("系统启动成功,运行在模拟模式")
# 保持程序运行
while True:
time.sleep(1)
else:
logger.error("系统启动失败")
simulator.stop()
return 1
except Exception as e:
logger.error(f"模拟运行错误: {str(e)}")
system.stop()
simulator.stop()
return 1
return 0
if __name__ == "__main__":
exit_code = run_simulation()
sys.exit(exit_code)

@ -0,0 +1,140 @@
import socket
import json
import logging
from threading import Thread, Lock
class CommunicationManager:
def __init__(self, host='0.0.0.0', port=5000):
"""
初始化通信管理器
:param host: 服务器主机地址
:param port: 服务器端口
"""
self.host = host
self.port = port
self.server_socket = None
self.clients = []
self.lock = Lock()
self.logger = logging.getLogger(__name__)
self.casualty_handler = None
self.supply_handler = None
def set_casualty_handler(self, handler):
"""
设置伤员处理回调函数
:param handler: 处理函数
"""
self.casualty_handler = handler
self.logger.info("已设置伤员处理回调函数")
def set_supply_handler(self, handler):
"""
设置物资处理回调函数
:param handler: 处理函数
"""
self.supply_handler = handler
self.logger.info("已设置物资处理回调函数")
def start_server(self):
"""启动通信服务器"""
try:
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.bind((self.host, self.port))
self.server_socket.listen(5)
self.logger.info(f"通信服务器启动在 {self.host}:{self.port}")
# 启动客户端接受线程
Thread(target=self._accept_clients, daemon=True).start()
except Exception as e:
self.logger.error(f"启动服务器失败: {str(e)}")
def _accept_clients(self):
"""接受新的客户端连接"""
while True:
try:
client_socket, address = self.server_socket.accept()
self.logger.info(f"新客户端连接: {address}")
with self.lock:
self.clients.append(client_socket)
# 为每个客户端启动一个处理线程
Thread(target=self._handle_client, args=(client_socket,), daemon=True).start()
except Exception as e:
self.logger.error(f"接受客户端连接失败: {str(e)}")
def _handle_client(self, client_socket):
"""处理客户端消息"""
while True:
try:
data = client_socket.recv(1024).decode('utf-8')
if not data:
break
message = json.loads(data)
self._process_message(message)
except Exception as e:
self.logger.error(f"处理客户端消息失败: {str(e)}")
break
# 客户端断开连接
with self.lock:
if client_socket in self.clients:
self.clients.remove(client_socket)
client_socket.close()
def _process_message(self, message):
"""
处理接收到的消息
:param message: 消息内容
"""
message_type = message.get('type')
if message_type == 'casualty_location':
self._handle_casualty_location(message)
elif message_type == 'medical_supply_request':
self._handle_medical_supply_request(message)
elif message_type == 'drone_status':
self._handle_drone_status(message)
def _handle_casualty_location(self, message):
"""处理伤员位置信息"""
location = message.get('location')
self.logger.info(f"收到伤员位置: {location}")
if self.casualty_handler:
self.casualty_handler(message)
def _handle_medical_supply_request(self, message):
"""处理医疗物资请求"""
supplies = message.get('supplies')
location = message.get('location')
self.logger.info(f"收到医疗物资请求: {supplies} 送往 {location}")
if self.supply_handler:
self.supply_handler(message)
def _handle_drone_status(self, message):
"""处理无人机状态信息"""
status = message.get('status')
self.logger.info(f"收到无人机状态: {status}")
# TODO: 实现无人机状态处理逻辑
def broadcast_message(self, message):
"""
广播消息给所有客户端
:param message: 要广播的消息
"""
message_str = json.dumps(message)
with self.lock:
for client in self.clients:
try:
client.send(message_str.encode('utf-8'))
except Exception as e:
self.logger.error(f"发送消息失败: {str(e)}")
def close(self):
"""关闭通信服务器"""
if self.server_socket:
self.server_socket.close()
self.logger.info("通信服务器已关闭")

@ -0,0 +1,103 @@
from dronekit import connect, VehicleMode, LocationGlobalRelative
import time
import logging
import math
class DroneController:
def __init__(self, connection_string):
"""
初始化无人机控制器
:param connection_string: 无人机连接字符串'udpin:localhost:14550'
"""
self.vehicle = None
self.connection_string = connection_string
self.logger = logging.getLogger(__name__)
def connect(self):
"""连接到无人机"""
try:
self.vehicle = connect(self.connection_string, wait_ready=True)
self.logger.info("成功连接到无人机")
return True
except Exception as e:
self.logger.error(f"连接无人机失败: {str(e)}")
return False
def arm_and_takeoff(self, target_altitude):
"""
起飞到指定高度
:param target_altitude: 目标高度
"""
self.logger.info("准备起飞...")
# 等待无人机准备就绪
while not self.vehicle.is_armable:
self.logger.info("等待无人机准备就绪...")
time.sleep(1)
# 切换到GUIDED模式
self.vehicle.mode = VehicleMode("GUIDED")
# 解锁无人机
self.vehicle.armed = True
# 等待无人机解锁
while not self.vehicle.armed:
self.logger.info("等待无人机解锁...")
time.sleep(1)
# 起飞
self.logger.info(f"起飞到高度: {target_altitude}")
self.vehicle.simple_takeoff(target_altitude)
# 等待到达目标高度
while True:
current_altitude = self.vehicle.location.global_relative_frame.alt
if current_altitude >= target_altitude * 0.95:
self.logger.info("到达目标高度")
break
time.sleep(1)
def goto_location(self, lat, lon, altitude):
"""
飞往指定位置
:param lat: 纬度
:param lon: 经度
:param altitude: 高度
"""
target_location = LocationGlobalRelative(lat, lon, altitude)
self.vehicle.simple_goto(target_location)
# 等待到达目标位置
while True:
current_location = self.vehicle.location.global_relative_frame
distance = self._get_distance_metres(current_location, target_location)
if distance < 1.0: # 距离小于1米认为到达
self.logger.info("到达目标位置")
break
time.sleep(1)
def land(self):
"""降落"""
self.logger.info("开始降落...")
self.vehicle.mode = VehicleMode("LAND")
# 等待降落完成
while self.vehicle.armed:
time.sleep(1)
self.logger.info("降落完成")
def close(self):
"""关闭连接"""
if self.vehicle:
self.vehicle.close()
self.logger.info("关闭无人机连接")
def _get_distance_metres(self, aLocation1, aLocation2):
"""
计算两点之间的距离
"""
dlat = aLocation2.lat - aLocation1.lat
dlong = aLocation2.lon - aLocation1.lon
return math.sqrt((dlat*dlat) + (dlong*dlong)) * 1.113195e5

@ -0,0 +1,190 @@
import logging
import time
from dataclasses import dataclass
from typing import Dict, List, Optional
@dataclass
class MedicalSupply:
"""医疗物资数据类"""
id: str
name: str
description: str
weight: float # 重量(克)
quantity: int # 数量
class MedicalSupplyManager:
"""医疗物资管理器"""
def __init__(self):
"""初始化医疗物资管理器"""
self.logger = logging.getLogger(__name__)
self.supplies = {} # 存储可用的医疗物资
self.supply_requests = {} # 存储物资请求记录
# 初始化默认物资
self._initialize_default_supplies()
def _initialize_default_supplies(self):
"""初始化默认物资"""
default_supplies = [
MedicalSupply(
id="first_aid_kit",
name="急救包",
description="基础急救包,包含绷带、消毒用品等",
weight=500,
quantity=10
),
MedicalSupply(
id="medicine_pack",
name="药品包",
description="包含止痛药、抗生素等常用药物",
weight=300,
quantity=20
),
MedicalSupply(
id="blood_plasma",
name="血浆",
description="应急血浆,通用型",
weight=450,
quantity=5
),
MedicalSupply(
id="surgical_kit",
name="手术包",
description="简易手术工具包",
weight=800,
quantity=3
),
]
for supply in default_supplies:
self.supplies[supply.id] = supply
def get_all_supplies(self) -> Dict[str, MedicalSupply]:
"""
获取所有可用的物资
:return: 物资字典
"""
return self.supplies.copy()
def get_supply(self, supply_id: str) -> Optional[MedicalSupply]:
"""
获取指定ID的物资
:param supply_id: 物资ID
:return: 物资对象或None
"""
return self.supplies.get(supply_id)
def add_supply(self, supply: MedicalSupply) -> bool:
"""
添加新的物资
:param supply: 物资对象
:return: 是否添加成功
"""
if supply.id in self.supplies:
self.logger.warning(f"物资ID已存在: {supply.id}")
return False
self.supplies[supply.id] = supply
self.logger.info(f"添加新物资: {supply.name}")
return True
def update_supply_quantity(self, supply_id: str, quantity: int) -> bool:
"""
更新物资数量
:param supply_id: 物资ID
:param quantity: 新数量
:return: 是否更新成功
"""
if supply_id not in self.supplies:
self.logger.warning(f"物资ID不存在: {supply_id}")
return False
self.supplies[supply_id].quantity = quantity
self.logger.info(f"更新物资数量: {supply_id}, 数量={quantity}")
return True
def create_supply_request(self,
target_id: str,
supply_id: str,
quantity: int) -> Optional[str]:
"""
创建物资请求
:param target_id: 目标ID伤员ID
:param supply_id: 物资ID
:param quantity: 请求数量
:return: 请求ID或None
"""
if supply_id not in self.supplies:
self.logger.warning(f"物资ID不存在: {supply_id}")
return None
if self.supplies[supply_id].quantity < quantity:
self.logger.warning(f"物资不足: 请求={quantity}, 可用={self.supplies[supply_id].quantity}")
return None
# 创建请求ID
request_id = f"req_{int(time.time())}_{supply_id}"
# 更新可用数量
self.supplies[supply_id].quantity -= quantity
# 存储请求
self.supply_requests[request_id] = {
'request_id': request_id,
'target_id': target_id,
'supply_id': supply_id,
'quantity': quantity,
'status': 'pending',
'timestamp': time.time()
}
self.logger.info(f"创建物资请求: ID={request_id}, 目标={target_id}, 物资={supply_id}, 数量={quantity}")
return request_id
def update_request_status(self, request_id: str, status: str) -> bool:
"""
更新请求状态
:param request_id: 请求ID
:param status: 新状态pending, in_progress, delivered, failed
:return: 是否更新成功
"""
if request_id not in self.supply_requests:
self.logger.warning(f"请求ID不存在: {request_id}")
return False
self.supply_requests[request_id]['status'] = status
self.logger.info(f"更新请求状态: ID={request_id}, 状态={status}")
# 如果请求失败,恢复物资数量
if status == 'failed':
supply_id = self.supply_requests[request_id]['supply_id']
quantity = self.supply_requests[request_id]['quantity']
self.supplies[supply_id].quantity += quantity
self.logger.info(f"恢复物资数量: ID={supply_id}, 增加={quantity}")
return True
def get_request(self, request_id: str) -> Optional[Dict]:
"""
获取请求信息
:param request_id: 请求ID
:return: 请求信息字典或None
"""
return self.supply_requests.get(request_id)
def get_requests_by_target(self, target_id: str) -> List[Dict]:
"""
获取指定目标的所有请求
:param target_id: 目标ID
:return: 请求信息列表
"""
return [req for req in self.supply_requests.values() if req['target_id'] == target_id]
def get_all_requests(self) -> Dict[str, Dict]:
"""
获取所有请求
:return: 请求字典
"""
return self.supply_requests.copy()

@ -0,0 +1,271 @@
import logging
import time
from enum import Enum
from typing import Dict, List, Optional, Tuple
from queue import Queue
from threading import Thread, Lock
class MissionType(Enum):
"""任务类型枚举"""
IDLE = 0
MEDICAL_SUPPLY = 1
SURVEILLANCE = 2
RETURN_TO_BASE = 3
class MissionStatus(Enum):
"""任务状态枚举"""
PENDING = 0
IN_PROGRESS = 1
COMPLETED = 2
FAILED = 3
class Mission:
"""任务类"""
def __init__(self,
mission_id: str,
mission_type: MissionType,
target_location: Tuple[float, float],
altitude: float = 10.0,
payload: Dict = None):
"""
初始化任务
:param mission_id: 任务ID
:param mission_type: 任务类型
:param target_location: 目标位置(纬度, 经度)
:param altitude: 飞行高度()
:param payload: 任务负载(如物资信息)
"""
self.mission_id = mission_id
self.mission_type = mission_type
self.target_location = target_location
self.altitude = altitude
self.payload = payload or {}
self.status = MissionStatus.PENDING
self.start_time = None
self.end_time = None
self.error_message = None
def to_dict(self) -> Dict:
"""转换成字典"""
return {
'mission_id': self.mission_id,
'mission_type': self.mission_type.name,
'target_location': self.target_location,
'altitude': self.altitude,
'payload': self.payload,
'status': self.status.name,
'start_time': self.start_time,
'end_time': self.end_time,
'error_message': self.error_message
}
class MissionPlanner:
"""任务规划器"""
def __init__(self, drone_controller):
"""
初始化任务规划器
:param drone_controller: 无人机控制器
"""
self.logger = logging.getLogger(__name__)
self.drone_controller = drone_controller
self.mission_queue = Queue()
self.current_mission = None
self.mission_history = []
self.lock = Lock()
self.running = False
self.worker_thread = None
def start(self):
"""启动任务规划器"""
if self.running:
self.logger.warning("任务规划器已经在运行")
return
self.running = True
self.worker_thread = Thread(target=self._mission_worker, daemon=True)
self.worker_thread.start()
self.logger.info("任务规划器已启动")
def stop(self):
"""停止任务规划器"""
if not self.running:
return
self.running = False
if self.worker_thread:
self.worker_thread.join(timeout=5)
self.logger.info("任务规划器已停止")
def add_mission(self, mission: Mission) -> bool:
"""
添加任务到队列
:param mission: 任务对象
:return: 是否添加成功
"""
try:
self.mission_queue.put(mission)
self.logger.info(f"添加任务: ID={mission.mission_id}, 类型={mission.mission_type.name}")
return True
except Exception as e:
self.logger.error(f"添加任务失败: {str(e)}")
return False
def get_current_mission(self) -> Optional[Mission]:
"""
获取当前执行的任务
:return: 当前任务或None
"""
with self.lock:
return self.current_mission
def get_mission_queue_size(self) -> int:
"""
获取任务队列大小
:return: 队列中的任务数
"""
return self.mission_queue.qsize()
def get_mission_history(self) -> List[Dict]:
"""
获取任务历史
:return: 任务历史记录列表
"""
with self.lock:
return [mission.to_dict() for mission in self.mission_history]
def _mission_worker(self):
"""任务工作线程"""
while self.running:
try:
if not self.mission_queue.empty():
# 从队列中获取下一个任务
mission = self.mission_queue.get()
with self.lock:
self.current_mission = mission
self.current_mission.status = MissionStatus.IN_PROGRESS
self.current_mission.start_time = time.time()
self.logger.info(f"开始执行任务: ID={mission.mission_id}, 类型={mission.mission_type.name}")
# 根据任务类型执行不同操作
success = self._execute_mission(mission)
with self.lock:
if success:
self.current_mission.status = MissionStatus.COMPLETED
self.logger.info(f"任务完成: ID={mission.mission_id}")
else:
self.current_mission.status = MissionStatus.FAILED
self.logger.error(f"任务失败: ID={mission.mission_id}")
self.current_mission.end_time = time.time()
self.mission_history.append(self.current_mission)
self.current_mission = None
self.mission_queue.task_done()
else:
# 无任务时等待
time.sleep(1)
except Exception as e:
self.logger.error(f"任务执行过程出错: {str(e)}")
if self.current_mission:
with self.lock:
self.current_mission.status = MissionStatus.FAILED
self.current_mission.error_message = str(e)
self.current_mission.end_time = time.time()
self.mission_history.append(self.current_mission)
self.current_mission = None
def _execute_mission(self, mission: Mission) -> bool:
"""
执行任务
:param mission: 任务对象
:return: 是否执行成功
"""
try:
if mission.mission_type == MissionType.MEDICAL_SUPPLY:
return self._execute_medical_supply_mission(mission)
elif mission.mission_type == MissionType.SURVEILLANCE:
return self._execute_surveillance_mission(mission)
elif mission.mission_type == MissionType.RETURN_TO_BASE:
return self._execute_return_to_base_mission(mission)
else:
self.logger.warning(f"未知任务类型: {mission.mission_type}")
return False
except Exception as e:
self.logger.error(f"执行任务失败: {str(e)}")
mission.error_message = str(e)
return False
def _execute_medical_supply_mission(self, mission: Mission) -> bool:
"""
执行医疗物资运送任务
:param mission: 任务对象
:return: 是否执行成功
"""
try:
# 飞往目标位置
lat, lon = mission.target_location
self.drone_controller.goto_location(lat, lon, mission.altitude)
# 模拟投放物资(实际应该有硬件操作)
self.logger.info(f"投放医疗物资: {mission.payload.get('supply_name', '未知物资')}")
time.sleep(2) # 模拟投放时间
# 返回基地
return self._execute_return_to_base_mission(mission)
except Exception as e:
self.logger.error(f"医疗物资任务执行失败: {str(e)}")
mission.error_message = str(e)
return False
def _execute_surveillance_mission(self, mission: Mission) -> bool:
"""
执行侦察任务
:param mission: 任务对象
:return: 是否执行成功
"""
try:
# 飞往目标位置
lat, lon = mission.target_location
self.drone_controller.goto_location(lat, lon, mission.altitude)
# 模拟侦察(实际应该有摄像头操作)
self.logger.info(f"进行区域侦察: ({lat}, {lon})")
time.sleep(5) # 模拟侦察时间
# 返回基地
return self._execute_return_to_base_mission(mission)
except Exception as e:
self.logger.error(f"侦察任务执行失败: {str(e)}")
mission.error_message = str(e)
return False
def _execute_return_to_base_mission(self, mission: Mission) -> bool:
"""
执行返回基地任务
:param mission: 任务对象
:return: 是否执行成功
"""
try:
# 获取基地位置(这里应该从配置中读取)
base_lat, base_lon = 39.9, 116.3 # 默认位置,实际应该从配置获取
# 飞往基地
self.logger.info("返回基地")
self.drone_controller.goto_location(base_lat, base_lon, mission.altitude)
# 降落
self.drone_controller.land()
return True
except Exception as e:
self.logger.error(f"返回基地任务执行失败: {str(e)}")
mission.error_message = str(e)
return False

@ -0,0 +1,131 @@
import logging
from geopy.geocoders import Nominatim
from geopy.distance import geodesic
import time
class PositionManager:
def __init__(self):
"""初始化定位管理器"""
self.logger = logging.getLogger(__name__)
self.geocoder = Nominatim(user_agent="medical_evac_system")
self.casualty_positions = {} # 存储伤员位置信息
def get_location_from_coordinates(self, lat, lon):
"""
根据经纬度获取位置描述
:param lat: 纬度
:param lon: 经度
:return: 位置描述
"""
try:
location = self.geocoder.reverse((lat, lon))
return location.address
except Exception as e:
self.logger.error(f"获取位置描述失败: {str(e)}")
return None
def calculate_distance(self, lat1, lon1, lat2, lon2):
"""
计算两点之间的距离
:param lat1: 起点纬度
:param lon1: 起点经度
:param lat2: 终点纬度
:param lon2: 终点经度
:return: 距离
"""
try:
point1 = (lat1, lon1)
point2 = (lat2, lon2)
distance = geodesic(point1, point2).meters
return distance
except Exception as e:
self.logger.error(f"计算距离失败: {str(e)}")
return None
def add_casualty_position(self, casualty_id, lat, lon, timestamp=None):
"""
添加伤员位置信息
:param casualty_id: 伤员ID
:param lat: 纬度
:param lon: 经度
:param timestamp: 时间戳
"""
if timestamp is None:
timestamp = time.time()
self.casualty_positions[casualty_id] = {
'latitude': lat,
'longitude': lon,
'timestamp': timestamp,
'location_description': self.get_location_from_coordinates(lat, lon)
}
self.logger.info(f"添加伤员位置: ID={casualty_id}, 位置=({lat}, {lon})")
def get_casualty_position(self, casualty_id):
"""
获取伤员位置信息
:param casualty_id: 伤员ID
:return: 位置信息字典
"""
return self.casualty_positions.get(casualty_id)
def update_casualty_position(self, casualty_id, lat, lon):
"""
更新伤员位置信息
:param casualty_id: 伤员ID
:param lat: 新的纬度
:param lon: 新的经度
"""
if casualty_id in self.casualty_positions:
self.add_casualty_position(casualty_id, lat, lon)
self.logger.info(f"更新伤员位置: ID={casualty_id}, 新位置=({lat}, {lon})")
else:
self.logger.warning(f"未找到伤员ID: {casualty_id}")
def get_nearest_casualty(self, lat, lon, max_distance=None):
"""
获取最近的伤员
:param lat: 当前位置纬度
:param lon: 当前位置经度
:param max_distance: 最大距离
:return: 最近的伤员信息
"""
nearest = None
min_distance = float('inf')
for casualty_id, position in self.casualty_positions.items():
distance = self.calculate_distance(
lat, lon,
position['latitude'],
position['longitude']
)
if distance is not None and distance < min_distance:
if max_distance is None or distance <= max_distance:
min_distance = distance
nearest = {
'casualty_id': casualty_id,
'distance': distance,
'position': position
}
return nearest
def get_all_casualties(self):
"""
获取所有伤员位置信息
:return: 伤员位置信息字典
"""
return self.casualty_positions.copy()
def remove_casualty(self, casualty_id):
"""
移除伤员位置信息
:param casualty_id: 伤员ID
"""
if casualty_id in self.casualty_positions:
del self.casualty_positions[casualty_id]
self.logger.info(f"移除伤员位置: ID={casualty_id}")
else:
self.logger.warning(f"未找到伤员ID: {casualty_id}")

@ -0,0 +1,342 @@
/* 全局样式 */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: #f0f4f8;
color: #333;
margin: 0;
padding: 0;
}
.navbar {
background: linear-gradient(90deg, #1a3a5f 0%, #2d5f8b 100%);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
padding: 15px 0;
}
.navbar-brand {
font-weight: bold;
font-size: 1.5rem;
letter-spacing: 0.5px;
color: white !important;
}
.container {
padding: 20px;
}
/* 地图样式 */
#map {
height: 550px;
width: 100%;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
border: 1px solid #e0e0e0;
}
/* 状态面板样式 */
.status-panel {
background-color: white;
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border-left: 4px solid #3498db;
}
.status-panel p {
margin-bottom: 8px;
display: flex;
justify-content: space-between;
}
.card {
margin-bottom: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border: none;
border-radius: 8px;
overflow: hidden;
}
.card-header {
font-weight: 600;
background-color: #f8fafc;
padding: 15px;
border-bottom: 1px solid #e0e6ed;
display: flex;
align-items: center;
}
.card-header i {
margin-right: 8px;
color: #3498db;
}
.card-body {
padding: 20px;
background-color: white;
}
/* 按钮样式 */
.btn-primary {
background: linear-gradient(45deg, #2d5f8b 0%, #3498db 100%);
border: none;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
padding: 10px 16px;
border-radius: 5px;
font-weight: 500;
transition: all 0.3s ease;
}
.btn-primary:hover {
background: linear-gradient(45deg, #3498db 0%, #2d5f8b 100%);
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.btn-secondary {
background-color: #f8f9fa;
color: #495057;
border: 1px solid #ced4da;
transition: all 0.3s ease;
}
.btn-secondary:hover {
background-color: #e2e6ea;
}
/* 列表样式 */
.casualty-item, .supply-item {
padding: 15px;
margin-bottom: 10px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
border-left: 4px solid #e74c3c;
transition: all 0.3s ease;
}
.supply-item {
border-left-color: #2ecc71;
}
.casualty-item:hover, .supply-item:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
/* 状态指示器 */
.status-indicator {
width: 12px;
height: 12px;
border-radius: 50%;
display: inline-block;
margin-right: 8px;
position: relative;
}
.status-idle {
background-color: #95a5a6;
}
.status-in-flight {
background-color: #3498db;
animation: pulse 1.5s infinite;
}
.status-warning {
background-color: #f39c12;
}
.status-error {
background-color: #e74c3c;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.5; }
100% { opacity: 1; }
}
/* 任务列表样式 */
.mission-list {
max-height: 300px;
overflow-y: auto;
padding-right: 5px;
}
.mission-list::-webkit-scrollbar {
width: 6px;
}
.mission-list::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
.mission-list::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 10px;
}
.mission-item {
padding: 12px 15px;
margin-bottom: 8px;
border-radius: 6px;
background-color: white;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.mission-pending {
border-left: 4px solid #95a5a6;
}
.mission-in-progress {
border-left: 4px solid #3498db;
}
.mission-completed {
border-left: 4px solid #2ecc71;
}
.mission-failed {
border-left: 4px solid #e74c3c;
}
/* 自定义地图标记样式 */
.casualty-marker .leaflet-popup-content-wrapper {
background-color: white;
border-radius: 8px;
border-left: 4px solid #e74c3c;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
}
.drone-marker .leaflet-popup-content-wrapper {
background-color: white;
border-radius: 8px;
border-left: 4px solid #3498db;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.2);
}
/* 响应式布局调整 */
@media (max-width: 768px) {
#map {
height: 400px;
}
.col-md-4 {
margin-top: 20px;
}
.card-header {
padding: 12px;
}
.card-body {
padding: 15px;
}
}
/* 地图标记样式 */
.map-marker {
width: 30px;
height: 30px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
}
.casualty-marker {
background-color: rgba(231, 76, 60, 0.9);
color: white;
font-size: 14px;
border: 2px solid white;
}
.drone-marker {
background-color: rgba(52, 152, 219, 0.9);
color: white;
font-size: 16px;
border: 2px solid white;
animation: pulse 1.5s infinite;
}
.popup-content {
min-width: 200px;
padding: 5px;
}
.popup-content h6 {
margin-bottom: 10px;
font-weight: 600;
border-bottom: 1px solid #eee;
padding-bottom: 5px;
}
.status-counter {
background-color: white;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.status-counter:hover {
transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.status-counter span {
font-size: 24px;
font-weight: bold;
color: #3498db;
}
.status-counter p {
margin-top: 5px;
margin-bottom: 0;
color: #7f8c8d;
font-size: 0.9rem;
}
/* 警告提示 */
.alert {
border-radius: 8px;
padding: 12px 15px;
margin-bottom: 15px;
border: none;
}
.alert-info {
background-color: #e9f7fe;
color: #3498db;
}
/* 高级表单控件 */
.form-label {
font-weight: 500;
margin-bottom: 8px;
color: #34495e;
}
.form-control, .form-select {
border-radius: 6px;
padding: 10px 12px;
border: 1px solid #dcdfe6;
transition: all 0.3s ease;
}
.form-control:focus, .form-select:focus {
border-color: #3498db;
box-shadow: 0 0 0 3px rgba(52, 152, 219, 0.25);
}
.badge {
padding: 6px 10px;
font-weight: 500;
border-radius: 6px;
}

@ -0,0 +1,451 @@
// 初始化应用
document.addEventListener('DOMContentLoaded', function() {
// 初始化地图
initMap();
// 初始化WebSocket连接
initWebSocket();
// 加载伤员数据
loadCasualties();
// 加载医疗物资数据
loadMedicalSupplies();
// 加载无人机状态
loadDroneStatus();
});
// 全局变量
let map = null;
let socket = null;
let casualtyMarkers = {};
let droneMarker = null;
let droneIcon = null;
let casualtyIcon = null;
// 初始化地图
function initMap() {
// 创建地图
map = L.map('map').setView([35.8617, 104.1954], 4); // 中国中心位置
// 添加OSM地图图层
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// 创建自定义图标
droneIcon = L.icon({
iconUrl: '/static/img/drone.png',
iconSize: [32, 32],
iconAnchor: [16, 16],
popupAnchor: [0, -16]
});
casualtyIcon = L.icon({
iconUrl: '/static/img/casualty.png',
iconSize: [32, 32],
iconAnchor: [16, 16],
popupAnchor: [0, -16]
});
// 地图点击事件 - 可以用于直接在地图上添加伤员
map.on('click', function(e) {
console.log("地图点击位置:", e.latlng.lat, e.latlng.lng);
});
}
// 初始化WebSocket连接
function initWebSocket() {
socket = io();
// 连接成功
socket.on('connect', function() {
console.log('WebSocket连接已建立');
updateConnectionStatus('已连接');
});
// 连接断开
socket.on('disconnect', function() {
console.log('WebSocket连接已断开');
updateConnectionStatus('已断开');
});
// 伤员位置更新
socket.on('casualty_location_updated', function(data) {
updateCasualtyMarker(data);
updateCasualtyList();
});
// 无人机状态更新
socket.on('drone_status_updated', function(data) {
updateDroneStatus(data);
});
// 医疗物资更新
socket.on('medical_supply_updated', function(data) {
updateSupplyStatus(data);
});
// 任务状态更新
socket.on('mission_status_updated', function(data) {
updateMissionStatus(data);
});
}
// 更新连接状态
function updateConnectionStatus(status) {
const connectionStatus = document.getElementById('connection-status');
if (connectionStatus) {
connectionStatus.textContent = status;
if (status === '已连接') {
connectionStatus.classList.remove('text-danger');
connectionStatus.classList.add('text-success');
} else {
connectionStatus.classList.remove('text-success');
connectionStatus.classList.add('text-danger');
}
}
}
// 更新伤员标记
function updateCasualtyMarker(data) {
const lat = data.latitude;
const lon = data.longitude;
const id = data.casualty_id;
if (casualtyMarkers[id]) {
casualtyMarkers[id].setLatLng([lat, lon]);
} else {
const marker = L.marker([lat, lon], {icon: casualtyIcon}).addTo(map);
marker.bindPopup(`
<div class="casualty-popup">
<h5>伤员ID: ${id}</h5>
<p>位置: ${lat.toFixed(6)}, ${lon.toFixed(6)}</p>
<button class="btn btn-sm btn-primary" onclick="requestMedicalSupply('${id}')">请求医疗物资</button>
</div>
`);
casualtyMarkers[id] = marker;
}
// 更新伤员列表UI
updateCasualtyList();
}
// 更新无人机状态
function updateDroneStatus(data) {
// 更新状态面板
document.getElementById('drone-status').textContent = data.status;
document.getElementById('drone-battery').textContent = data.battery + '%';
document.getElementById('drone-location').textContent =
`纬度: ${data.latitude.toFixed(6)}, 经度: ${data.longitude.toFixed(6)}`;
// 根据电量变化状态颜色
const batteryLevel = parseInt(data.battery);
const batteryElement = document.getElementById('drone-battery');
if (batteryLevel < 20) {
batteryElement.className = 'text-danger';
} else if (batteryLevel < 50) {
batteryElement.className = 'text-warning';
} else {
batteryElement.className = 'text-success';
}
// 更新无人机标记
if (droneMarker) {
droneMarker.setLatLng([data.latitude, data.longitude]);
} else {
droneMarker = L.marker([data.latitude, data.longitude], {icon: droneIcon}).addTo(map);
droneMarker.bindPopup(`
<div class="drone-popup">
<h5>无人机状态</h5>
<p>状态: ${data.status}</p>
<p>电量: ${data.battery}%</p>
</div>
`);
}
}
// 更新物资状态
function updateSupplyStatus(data) {
// 更新物资列表
const supplyList = document.getElementById('supply-list');
if (!supplyList) return;
supplyList.innerHTML = '';
if (data.supplies && data.supplies.length > 0) {
data.supplies.forEach(supply => {
const supplyItem = document.createElement('div');
supplyItem.className = 'supply-item';
supplyItem.innerHTML = `
<div class="d-flex justify-content-between align-items-center">
<div>
<strong>${supply.name}</strong> (${supply.quantity})
</div>
<button class="btn btn-sm btn-outline-primary" onclick="requestSupply('${supply.id}')">
请求
</button>
</div>
<small class="text-muted">${supply.description}</small>
`;
supplyList.appendChild(supplyItem);
});
} else {
supplyList.innerHTML = '<p class="text-muted">没有可用物资</p>';
}
}
// 更新任务状态
function updateMissionStatus(data) {
// 更新任务列表
const missionList = document.getElementById('mission-list');
if (!missionList) return;
if (data.currentMission) {
// 显示当前任务
const currentMissionElement = document.getElementById('current-mission');
if (currentMissionElement) {
currentMissionElement.innerHTML = `
<div class="mission-item mission-${data.currentMission.status.toLowerCase()}">
<div class="d-flex justify-content-between">
<strong>${getMissionTypeLabel(data.currentMission.mission_type)}</strong>
<span class="badge bg-${getMissionStatusBadge(data.currentMission.status)}">
${data.currentMission.status}
</span>
</div>
<div>ID: ${data.currentMission.mission_id}</div>
<div>目标: (${data.currentMission.target_location[0].toFixed(6)},
${data.currentMission.target_location[1].toFixed(6)})</div>
</div>
`;
}
}
// 更新历史任务列表
if (data.missionHistory && data.missionHistory.length > 0) {
missionList.innerHTML = '';
data.missionHistory.forEach(mission => {
const missionItem = document.createElement('div');
missionItem.className = `mission-item mission-${mission.status.toLowerCase()}`;
missionItem.innerHTML = `
<div class="d-flex justify-content-between">
<strong>${getMissionTypeLabel(mission.mission_type)}</strong>
<span class="badge bg-${getMissionStatusBadge(mission.status)}">
${mission.status}
</span>
</div>
<div>ID: ${mission.mission_id}</div>
<small class="text-muted">${formatTime(mission.start_time)}</small>
`;
missionList.appendChild(missionItem);
});
} else {
missionList.innerHTML = '<p class="text-muted">没有历史任务</p>';
}
}
// 获取任务类型标签
function getMissionTypeLabel(missionType) {
switch (missionType) {
case 'MEDICAL_SUPPLY': return '医疗物资运送';
case 'SURVEILLANCE': return '区域侦察';
case 'RETURN_TO_BASE': return '返回基地';
default: return missionType;
}
}
// 获取任务状态标签样式
function getMissionStatusBadge(status) {
switch (status) {
case 'PENDING': return 'secondary';
case 'IN_PROGRESS': return 'primary';
case 'COMPLETED': return 'success';
case 'FAILED': return 'danger';
default: return 'secondary';
}
}
// 格式化时间
function formatTime(timestamp) {
if (!timestamp) return '';
const date = new Date(timestamp * 1000);
return date.toLocaleString();
}
// 加载伤员数据
function loadCasualties() {
fetch('/api/casualties')
.then(response => response.json())
.then(data => {
if (data.casualties && data.casualties.length > 0) {
data.casualties.forEach(casualty => {
updateCasualtyMarker(casualty);
});
}
})
.catch(error => console.error('加载伤员数据失败:', error));
}
// 更新伤员列表
function updateCasualtyList() {
const casualtyList = document.getElementById('casualty-list');
if (!casualtyList) return;
// 清空现有列表
casualtyList.innerHTML = '';
// 遍历所有伤员标记
for (const id in casualtyMarkers) {
const marker = casualtyMarkers[id];
const position = marker.getLatLng();
const casualtyItem = document.createElement('div');
casualtyItem.className = 'casualty-item';
casualtyItem.innerHTML = `
<div class="d-flex justify-content-between align-items-center">
<div>
<strong>伤员ID: ${id}</strong>
</div>
<button class="btn btn-sm btn-outline-primary" onclick="requestMedicalSupply('${id}')">
请求物资
</button>
</div>
<div>位置: ${position.lat.toFixed(6)}, ${position.lng.toFixed(6)}</div>
`;
casualtyList.appendChild(casualtyItem);
}
// 如果没有伤员,显示提示信息
if (Object.keys(casualtyMarkers).length === 0) {
casualtyList.innerHTML = '<p class="text-muted">没有伤员数据</p>';
}
}
// 加载医疗物资数据
function loadMedicalSupplies() {
fetch('/api/medical-supplies')
.then(response => response.json())
.then(data => {
updateSupplyStatus(data);
})
.catch(error => console.error('加载医疗物资失败:', error));
}
// 加载无人机状态
function loadDroneStatus() {
fetch('/api/drone/status')
.then(response => response.json())
.then(data => {
updateDroneStatus(data);
})
.catch(error => console.error('加载无人机状态失败:', error));
}
// 添加伤员
function addCasualty() {
const modal = new bootstrap.Modal(document.getElementById('addCasualtyModal'));
modal.show();
}
// 提交伤员信息
function submitCasualty() {
const id = document.getElementById('casualty-id').value;
const lat = parseFloat(document.getElementById('casualty-lat').value);
const lon = parseFloat(document.getElementById('casualty-lon').value);
if (!id || isNaN(lat) || isNaN(lon)) {
alert('请填写完整的伤员信息');
return;
}
const data = {
casualty_id: id,
latitude: lat,
longitude: lon
};
// 通过API发送
fetch('/api/casualties', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.status === 'success') {
// 关闭模态框
bootstrap.Modal.getInstance(document.getElementById('addCasualtyModal')).hide();
// 通过WebSocket发送更新
socket.emit('update_casualty_location', data);
} else {
alert('添加伤员失败: ' + result.message);
}
})
.catch(error => {
console.error('添加伤员失败:', error);
alert('添加伤员失败,请检查网络连接');
});
}
// 请求医疗物资
function requestMedicalSupply(casualtyId) {
// 打开请求物资模态框
const modal = new bootstrap.Modal(document.getElementById('requestSuppliesModal'));
document.getElementById('supply-target').value = casualtyId;
modal.show();
}
// 提交医疗物资请求
function submitSupplyRequest() {
const type = document.getElementById('supply-type').value;
const quantity = parseInt(document.getElementById('supply-quantity').value);
const target = document.getElementById('supply-target').value;
if (!type || isNaN(quantity) || quantity <= 0 || !target) {
alert('请填写完整的物资请求信息');
return;
}
const data = {
type: type,
quantity: quantity,
target_id: target
};
// 通过API发送
fetch('/api/medical-supplies', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
.then(response => response.json())
.then(result => {
if (result.status === 'success') {
// 关闭模态框
bootstrap.Modal.getInstance(document.getElementById('requestSuppliesModal')).hide();
// 通过WebSocket发送更新
socket.emit('request_medical_supplies', data);
} else {
alert('请求物资失败: ' + result.message);
}
})
.catch(error => {
console.error('请求物资失败:', error);
alert('请求物资失败,请检查网络连接');
});
}

@ -0,0 +1,617 @@
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能战场医疗后送系统</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css">
<link href="/static/css/styles.css" rel="stylesheet">
<style>
#map {
height: 500px;
width: 100%;
}
.status-panel {
background-color: #f8f9fa;
padding: 15px;
border-radius: 5px;
margin-bottom: 15px;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container">
<a class="navbar-brand" href="#">
<i class="fas fa-helicopter-symbol me-2"></i>
智能战场医疗后送系统
</a>
<span class="navbar-text text-white ms-auto">
<i class="fas fa-signal me-1"></i> 系统状态: <span id="system-status">在线</span>
</span>
</div>
</nav>
<div class="container mt-4">
<div class="row">
<!-- 左侧地图面板 -->
<div class="col-md-8">
<div class="card">
<div class="card-header">
<i class="fas fa-map-marked-alt"></i> 战场地图
<div class="float-end">
<button class="btn btn-sm btn-outline-secondary" id="center-map">
<i class="fas fa-crosshairs"></i> 中心定位
</button>
</div>
</div>
<div class="card-body p-0">
<div id="map"></div>
</div>
</div>
<!-- 任务状态面板 -->
<div class="card mt-3">
<div class="card-header">
<i class="fas fa-tasks"></i> 任务状态
</div>
<div class="card-body">
<div class="row">
<div class="col-md-3 text-center">
<div class="status-counter">
<span id="pending-missions-count">0</span>
<p>待处理任务</p>
</div>
</div>
<div class="col-md-3 text-center">
<div class="status-counter">
<span id="in-progress-missions-count">0</span>
<p>进行中任务</p>
</div>
</div>
<div class="col-md-3 text-center">
<div class="status-counter">
<span id="completed-missions-count">0</span>
<p>已完成任务</p>
</div>
</div>
<div class="col-md-3 text-center">
<div class="status-counter">
<span id="failed-missions-count">0</span>
<p>失败任务</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 右侧控制面板 -->
<div class="col-md-4">
<!-- 无人机状态 -->
<div class="card mb-3">
<div class="card-header">
<i class="fas fa-drone"></i> 无人机状态
<span class="badge bg-success float-end" id="drone-connection-status">已连接</span>
</div>
<div class="card-body">
<div class="status-panel">
<p>
<span><i class="fas fa-info-circle"></i> 状态:</span>
<span><span class="status-indicator status-in-flight"></span><span id="drone-status">待机</span></span>
</p>
<p>
<span><i class="fas fa-battery-half"></i> 电量:</span>
<span id="drone-battery">100%</span>
</p>
<p>
<span><i class="fas fa-location-dot"></i> 位置:</span>
<span id="drone-location">未获取</span>
</p>
<p>
<span><i class="fas fa-gauge-high"></i> 速度:</span>
<span id="drone-speed">0 m/s</span>
</p>
</div>
</div>
</div>
<!-- 伤员信息 -->
<div class="card mb-3">
<div class="card-header">
<i class="fas fa-user-injured"></i> 伤员信息
<span class="badge bg-primary float-end" id="casualty-count">0</span>
</div>
<div class="card-body">
<div id="casualty-list" class="mission-list">
<!-- 伤员列表将通过JavaScript动态添加 -->
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i> 尚无伤员信息
</div>
</div>
<button class="btn btn-primary mt-3 w-100" onclick="addCasualty()">
<i class="fas fa-plus me-2"></i> 添加伤员
</button>
</div>
</div>
<!-- 医疗物资 -->
<div class="card mb-3">
<div class="card-header">
<i class="fas fa-kit-medical"></i> 医疗物资
</div>
<div class="card-body">
<div id="supply-list" class="mission-list">
<!-- 物资列表将通过JavaScript动态添加 -->
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i> 正在加载物资信息...
</div>
</div>
<button class="btn btn-primary mt-3 w-100" onclick="requestSupplies()">
<i class="fas fa-paper-plane me-2"></i> 请求物资
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 添加伤员模态框 -->
<div class="modal fade" id="addCasualtyModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-user-injured me-2"></i> 添加伤员</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="casualty-form">
<div class="mb-3">
<label class="form-label"><i class="fas fa-id-card me-1"></i> 伤员ID</label>
<input type="text" class="form-control" id="casualty-id" required>
</div>
<div class="mb-3">
<label class="form-label"><i class="fas fa-location-arrow me-1"></i> 纬度</label>
<input type="number" class="form-control" id="casualty-lat" step="any" required>
</div>
<div class="mb-3">
<label class="form-label"><i class="fas fa-location-arrow me-1"></i> 经度</label>
<input type="number" class="form-control" id="casualty-lon" step="any" required>
</div>
<div class="mb-3">
<label class="form-label"><i class="fas fa-info-circle me-1"></i> 状态描述</label>
<select class="form-select" id="casualty-status">
<option value="critical">危重</option>
<option value="serious">严重</option>
<option value="stable">稳定</option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i> 取消
</button>
<button type="button" class="btn btn-primary" onclick="submitCasualty()">
<i class="fas fa-check me-1"></i> 提交
</button>
</div>
</div>
</div>
</div>
<!-- 请求物资模态框 -->
<div class="modal fade" id="requestSuppliesModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-kit-medical me-2"></i> 请求医疗物资</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<form id="supply-form">
<div class="mb-3">
<label class="form-label"><i class="fas fa-tag me-1"></i> 物资类型</label>
<select class="form-select" id="supply-type" required>
<option value="first_aid">急救包</option>
<option value="medicine">药品</option>
<option value="equipment">医疗设备</option>
<option value="blood">血液</option>
<option value="oxygen">氧气</option>
</select>
</div>
<div class="mb-3">
<label class="form-label"><i class="fas fa-hashtag me-1"></i> 数量</label>
<input type="number" class="form-control" id="supply-quantity" min="1" value="1" required>
</div>
<div class="mb-3">
<label class="form-label"><i class="fas fa-user-injured me-1"></i> 目标位置伤员ID</label>
<select class="form-select" id="supply-target" required>
<!-- 将通过JavaScript动态填充 -->
</select>
</div>
<div class="mb-3">
<label class="form-label"><i class="fas fa-info-circle me-1"></i> 优先级</label>
<select class="form-select" id="supply-priority">
<option value="high"></option>
<option value="medium" selected></option>
<option value="low"></option>
</select>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="fas fa-times me-1"></i> 取消
</button>
<button type="button" class="btn btn-primary" onclick="submitSupplyRequest()">
<i class="fas fa-paper-plane me-1"></i> 提交
</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js"></script>
<script>
// 初始化地图,使用更轻量级的配置
var map = L.map('map', {
preferCanvas: true, // 使用Canvas渲染提高性能
zoomControl: true,
attributionControl: true
}).setView([39.9042, 116.4074], 12); // 北京中心位置
// 添加多个地图图层选择
var baseMaps = {};
// 标准地图
var standardMap = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
maxZoom: 19
}).addTo(map);
baseMaps["标准地图"] = standardMap;
// 地形图 (使用Stamen Terrain)
var terrainMap = L.tileLayer('https://stamen-tiles-{s}.a.ssl.fastly.net/terrain/{z}/{x}/{y}{r}.png', {
attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
maxZoom: 18
});
baseMaps["地形图"] = terrainMap;
// 卫星图 (使用Esri)
var satelliteMap = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community',
maxZoom: 18
});
baseMaps["卫星图"] = satelliteMap;
// 添加图层控制器
L.control.layers(baseMaps, null, {position: 'topright'}).addTo(map);
// 添加北京中心点标记
var beijingMarker = L.marker([39.9042, 116.4074]).addTo(map);
beijingMarker.bindPopup("<b>北京市中心</b>").openPopup();
// 添加比例尺
L.control.scale({imperial: false, metric: true, position: 'bottomleft'}).addTo(map);
// 初始化WebSocket连接
var socket = io();
// 伤员标记
var casualtyMarkers = {};
// 无人机标记
var droneMarker = null;
// 添加中心定位按钮事件
document.getElementById('center-map').addEventListener('click', function() {
if (droneMarker) {
map.setView(droneMarker.getLatLng(), 14);
} else {
map.setView([39.9042, 116.4074], 12);
}
});
// 添加一些预设的伤员(模拟数据)
function addPresetCasualties() {
var casualties = [
{casualty_id: "casualty_001", latitude: 39.915, longitude: 116.404, status: "critical"},
{casualty_id: "casualty_002", latitude: 39.909, longitude: 116.417, status: "serious"},
{casualty_id: "casualty_003", latitude: 39.897, longitude: 116.399, status: "stable"}
];
casualties.forEach(function(casualty) {
updateCasualtyMarker(casualty);
});
}
// 页面加载完成后添加预设伤员
window.addEventListener('load', function() {
setTimeout(addPresetCasualties, 2000); // 延迟2秒添加确保地图已加载
});
// WebSocket事件处理
socket.on('connect', function() {
console.log('WebSocket连接已建立');
});
socket.on('casualty_location_updated', function(data) {
updateCasualtyMarker(data);
});
socket.on('drone_status_updated', function(data) {
updateDroneStatus(data);
});
socket.on('medical_supply_updated', function(data) {
updateSupplyStatus(data);
});
// 更新伤员标记
function updateCasualtyMarker(data) {
var lat = data.latitude;
var lon = data.longitude;
var id = data.casualty_id;
var status = data.status || 'unknown';
// 更新伤员列表
var listItem = document.createElement('div');
listItem.className = 'casualty-item';
listItem.innerHTML = `
<div class="d-flex justify-content-between align-items-center">
<div>
<strong><i class="fas fa-user-injured me-1"></i> 伤员ID:</strong> ${id}
</div>
<span class="badge ${status === 'critical' ? 'bg-danger' :
status === 'serious' ? 'bg-warning' :
'bg-info'}">${status}</span>
</div>
<div class="mt-2">
<small><i class="fas fa-location-dot me-1"></i> 位置: ${lat.toFixed(6)}, ${lon.toFixed(6)}</small>
</div>
`;
var casualtyList = document.getElementById('casualty-list');
// 清除"无伤员"提示
if (casualtyList.querySelector('.alert')) {
casualtyList.innerHTML = '';
}
// 检查是否已存在相同ID的伤员
var existingItems = casualtyList.querySelectorAll('.casualty-item');
let exists = false;
existingItems.forEach(item => {
if (item.getAttribute('data-id') === id) {
casualtyList.replaceChild(listItem, item);
exists = true;
}
});
if (!exists) {
casualtyList.appendChild(listItem);
}
listItem.setAttribute('data-id', id);
// 更新伤员计数
document.getElementById('casualty-count').textContent = casualtyList.querySelectorAll('.casualty-item').length;
// 更新物资请求下拉框中的伤员列表
updateCasualtyDropdown();
// 在地图上添加或更新标记
if (casualtyMarkers[id]) {
casualtyMarkers[id].setLatLng([lat, lon]);
} else {
var icon = L.divIcon({
className: 'casualty-marker-icon',
html: `<div class="map-marker casualty-marker"><i class="fas fa-user-injured"></i></div>`,
iconSize: [30, 30],
iconAnchor: [15, 15]
});
var marker = L.marker([lat, lon], {icon: icon}).addTo(map);
marker.bindPopup(`
<div class="popup-content">
<h6>伤员信息</h6>
<p><strong>ID:</strong> ${id}</p>
<p><strong>状态:</strong> ${status}</p>
<p><strong>位置:</strong> ${lat.toFixed(6)}, ${lon.toFixed(6)}</p>
<button class="btn btn-sm btn-primary request-supply-btn" data-id="${id}">请求物资</button>
</div>
`);
marker.on('popupopen', function() {
document.querySelector('.request-supply-btn').addEventListener('click', function() {
var casualtyId = this.getAttribute('data-id');
document.getElementById('supply-target').value = casualtyId;
var modal = new bootstrap.Modal(document.getElementById('requestSuppliesModal'));
modal.show();
});
});
casualtyMarkers[id] = marker;
}
}
// 更新伤员下拉框
function updateCasualtyDropdown() {
var dropdown = document.getElementById('supply-target');
dropdown.innerHTML = '';
var casualtyList = document.getElementById('casualty-list');
var items = casualtyList.querySelectorAll('.casualty-item');
items.forEach(item => {
var id = item.getAttribute('data-id');
var option = document.createElement('option');
option.value = id;
option.textContent = id;
dropdown.appendChild(option);
});
}
// 更新无人机状态
function updateDroneStatus(data) {
document.getElementById('drone-status').textContent = data.status;
// 更新状态指示器类
var indicator = document.querySelector('.status-indicator');
indicator.className = 'status-indicator';
if (data.status === '执行任务') {
indicator.classList.add('status-in-flight');
document.getElementById('drone-connection-status').textContent = '执行任务中';
document.getElementById('drone-connection-status').className = 'badge bg-primary float-end';
} else if (data.status === '待机') {
indicator.classList.add('status-idle');
document.getElementById('drone-connection-status').textContent = '待命中';
document.getElementById('drone-connection-status').className = 'badge bg-success float-end';
} else if (data.status === '未连接') {
indicator.classList.add('status-error');
document.getElementById('drone-connection-status').textContent = '未连接';
document.getElementById('drone-connection-status').className = 'badge bg-danger float-end';
}
document.getElementById('drone-battery').textContent = data.battery + '%';
// 添加电池指示图标
var batteryLevel = data.battery;
var batteryIcon = '';
if (batteryLevel > 75) {
batteryIcon = '<i class="fas fa-battery-full text-success me-1"></i> ';
} else if (batteryLevel > 50) {
batteryIcon = '<i class="fas fa-battery-three-quarters text-success me-1"></i> ';
} else if (batteryLevel > 25) {
batteryIcon = '<i class="fas fa-battery-half text-warning me-1"></i> ';
} else {
batteryIcon = '<i class="fas fa-battery-quarter text-danger me-1"></i> ';
}
document.getElementById('drone-battery').innerHTML = batteryIcon + batteryLevel + '%';
document.getElementById('drone-location').textContent =
`纬度: ${data.latitude.toFixed(6)}, 经度: ${data.longitude.toFixed(6)}`;
// 添加速度信息
document.getElementById('drone-speed').textContent = (data.speed || 0) + ' m/s';
// 更新无人机标记
if (droneMarker) {
droneMarker.setLatLng([data.latitude, data.longitude]);
} else {
var icon = L.divIcon({
className: 'drone-marker-icon',
html: `<div class="map-marker drone-marker"><i class="fas fa-helicopter"></i></div>`,
iconSize: [40, 40],
iconAnchor: [20, 20]
});
droneMarker = L.marker([data.latitude, data.longitude], {icon: icon}).addTo(map);
droneMarker.bindPopup(`
<div class="popup-content">
<h6>无人机状态</h6>
<p><strong>状态:</strong> <span id="popup-drone-status">${data.status}</span></p>
<p><strong>电量:</strong> <span id="popup-drone-battery">${data.battery}%</span></p>
<p><strong>高度:</strong> <span id="popup-drone-altitude">${data.altitude || 0} m</span></p>
</div>
`);
}
}
// 更新物资状态
function updateSupplyStatus(data) {
var supplyList = document.getElementById('supply-list');
supplyList.innerHTML = '';
if (!data.supplies || data.supplies.length === 0) {
supplyList.innerHTML = `
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i> 暂无物资信息
</div>
`;
return;
}
data.supplies.forEach(supply => {
var item = document.createElement('div');
item.className = 'supply-item';
// 根据物资类型选择图标
let icon = 'fa-kit-medical';
if (supply.id === 'medicine') icon = 'fa-pills';
else if (supply.id === 'equipment') icon = 'fa-stethoscope';
else if (supply.id === 'blood') icon = 'fa-droplet';
else if (supply.id === 'oxygen') icon = 'fa-lungs';
item.innerHTML = `
<div class="d-flex justify-content-between align-items-center">
<div>
<strong><i class="fas ${icon} me-1"></i> ${supply.name}:</strong>
</div>
<span class="badge bg-${supply.quantity > 5 ? 'success' : supply.quantity > 2 ? 'warning' : 'danger'}">${supply.quantity}</span>
</div>
<div class="mt-1">
<small>${supply.description || '无描述'}</small>
</div>
`;
supplyList.appendChild(item);
});
}
// 添加伤员
function addCasualty() {
var modal = new bootstrap.Modal(document.getElementById('addCasualtyModal'));
modal.show();
}
// 提交伤员信息
function submitCasualty() {
var id = document.getElementById('casualty-id').value;
var lat = document.getElementById('casualty-lat').value;
var lon = document.getElementById('casualty-lon').value;
var status = document.getElementById('casualty-status').value;
socket.emit('update_casualty_location', {
casualty_id: id,
latitude: parseFloat(lat),
longitude: parseFloat(lon),
status: status
});
bootstrap.Modal.getInstance(document.getElementById('addCasualtyModal')).hide();
}
// 请求物资
function requestSupplies() {
var modal = new bootstrap.Modal(document.getElementById('requestSuppliesModal'));
modal.show();
}
// 提交物资请求
function submitSupplyRequest() {
var type = document.getElementById('supply-type').value;
var quantity = document.getElementById('supply-quantity').value;
var target = document.getElementById('supply-target').value;
var priority = document.getElementById('supply-priority').value;
socket.emit('request_medical_supplies', {
type: type,
quantity: parseInt(quantity),
target: target,
priority: priority
});
bootstrap.Modal.getInstance(document.getElementById('requestSuppliesModal')).hide();
}
</script>
</body>
</html>

@ -0,0 +1,191 @@
from flask import Flask, render_template, jsonify, request, send_from_directory
from flask_socketio import SocketIO, emit
import logging
import os
import json
from functools import wraps
class WebInterface:
def __init__(self, host='0.0.0.0', port=8080):
"""
初始化Web界面
:param host: 服务器主机地址
:param port: 服务器端口
"""
self.app = Flask(__name__, static_folder='static', template_folder='templates')
self.socketio = SocketIO(self.app, cors_allowed_origins="*")
self.host = host
self.port = port
self.logger = logging.getLogger(__name__)
# 处理器
self.casualty_handler = None
self.supply_handler = None
self.drone_status_handler = None
# 注册路由
self._register_routes()
def set_casualty_handler(self, handler):
"""设置伤员处理器"""
self.casualty_handler = handler
def set_supply_handler(self, handler):
"""设置物资处理器"""
self.supply_handler = handler
def set_drone_status_handler(self, handler):
"""设置无人机状态处理器"""
self.drone_status_handler = handler
def _register_routes(self):
"""注册Web路由"""
# 主页
@self.app.route('/')
def index():
return render_template('index.html')
# 静态文件
@self.app.route('/static/<path:path>')
def serve_static(path):
return send_from_directory(self.app.static_folder, path)
# 伤员API
@self.app.route('/api/casualties', methods=['GET'])
def get_casualties():
try:
if self.casualty_handler:
casualties = []
# 实际应该调用position_manager.get_all_casualties()
return jsonify({'casualties': casualties, 'status': 'success'})
return jsonify({'casualties': [], 'status': 'error', 'message': '伤员处理器未设置'})
except Exception as e:
self.logger.error(f"获取伤员数据失败: {str(e)}")
return jsonify({'casualties': [], 'status': 'error', 'message': str(e)})
@self.app.route('/api/casualties', methods=['POST'])
def add_casualty():
try:
data = request.get_json()
if self.casualty_handler:
result = self.casualty_handler(data)
return jsonify({'status': 'success' if result else 'error'})
return jsonify({'status': 'error', 'message': '伤员处理器未设置'})
except Exception as e:
self.logger.error(f"添加伤员失败: {str(e)}")
return jsonify({'status': 'error', 'message': str(e)})
# 医疗物资API
@self.app.route('/api/medical-supplies', methods=['GET'])
def get_medical_supplies():
try:
# 实际应该调用medical_supply_manager.get_all_supplies()
return jsonify({'supplies': [], 'status': 'success'})
except Exception as e:
self.logger.error(f"获取医疗物资失败: {str(e)}")
return jsonify({'supplies': [], 'status': 'error', 'message': str(e)})
@self.app.route('/api/medical-supplies', methods=['POST'])
def request_medical_supplies():
try:
data = request.get_json()
if self.supply_handler:
result = self.supply_handler(data)
return jsonify({'status': 'success' if result else 'error'})
return jsonify({'status': 'error', 'message': '物资处理器未设置'})
except Exception as e:
self.logger.error(f"请求医疗物资失败: {str(e)}")
return jsonify({'status': 'error', 'message': str(e)})
# 无人机状态API
@self.app.route('/api/drone/status', methods=['GET'])
def get_drone_status():
try:
if self.drone_status_handler:
status = self.drone_status_handler()
return jsonify(status)
return jsonify({'status': 'error', 'message': '无人机状态处理器未设置'})
except Exception as e:
self.logger.error(f"获取无人机状态失败: {str(e)}")
return jsonify({'status': 'error', 'message': str(e)})
# WebSocket事件处理
@self.socketio.on('connect')
def handle_connect():
self.logger.info("客户端连接")
@self.socketio.on('disconnect')
def handle_disconnect():
self.logger.info("客户端断开连接")
@self.socketio.on('update_casualty_location')
def handle_casualty_location(data):
try:
if self.casualty_handler:
result = self.casualty_handler(data)
if not result:
self.logger.warning("处理伤员位置更新失败")
else:
self.logger.warning("伤员处理器未设置")
except Exception as e:
self.logger.error(f"处理伤员位置更新失败: {str(e)}")
@self.socketio.on('request_medical_supplies')
def handle_medical_supplies(data):
try:
if self.supply_handler:
result = self.supply_handler(data)
if not result:
self.logger.warning("处理医疗物资请求失败")
else:
self.logger.warning("物资处理器未设置")
except Exception as e:
self.logger.error(f"处理医疗物资请求失败: {str(e)}")
@self.socketio.on('update_drone_status')
def handle_drone_status():
try:
if self.drone_status_handler:
self.drone_status_handler()
else:
self.logger.warning("无人机状态处理器未设置")
except Exception as e:
self.logger.error(f"处理无人机状态更新失败: {str(e)}")
def start(self):
"""启动Web服务器"""
try:
self.logger.info(f"启动Web服务器: {self.host}:{self.port}")
self.socketio.run(self.app, host=self.host, port=self.port)
except Exception as e:
self.logger.error(f"启动Web服务器失败: {str(e)}")
def broadcast_casualty_update(self, casualty_data):
"""
广播伤员信息更新
:param casualty_data: 伤员数据
"""
try:
self.socketio.emit('casualty_location_updated', casualty_data)
except Exception as e:
self.logger.error(f"广播伤员更新失败: {str(e)}")
def broadcast_drone_update(self, drone_data):
"""
广播无人机状态更新
:param drone_data: 无人机数据
"""
try:
self.socketio.emit('drone_status_updated', drone_data)
except Exception as e:
self.logger.error(f"广播无人机状态更新失败: {str(e)}")
def broadcast_medical_supply_update(self, supply_data):
"""
广播医疗物资状态更新
:param supply_data: 医疗物资数据
"""
try:
self.socketio.emit('medical_supply_updated', supply_data)
except Exception as e:
self.logger.error(f"广播医疗物资更新失败: {str(e)}")

@ -0,0 +1,619 @@
文档编号:智能战场医疗后送系统 SDS 1.0
# 智能战场医疗后送系统
# 软件设计规格说明书
日期2023-06-15
## 文档变更历史记录
| 序号 | 变更日期 | 变更人员 | 变更内容详情描述 | 版本 |
|------|----------|----------|------------------|------|
| 1 | 2023-06-15 | 系统架构师 | 创建文档初稿 | 1.0 |
| | | | | |
| | | | | |
## 目录
1. [引言](#1引言)
1. [编写目的](#11-编写目的)
2. [读者对象](#12-读者对象)
3. [软件项目概述](#13-软件项目概述)
4. [文档概述](#14-文档概述)
5. [定义](#15-定义)
6. [参考资料](#16-参考资料)
2. [软件设计约束](#2软件设计约束)
1. [软件设计目标和原则](#21-软件设计目标和原则)
2. [软件设计的约束和限制](#22-软件设计的约束和限制)
3. [软件设计](#3软件设计)
1. [软件体系结构设计](#31-软件体系结构设计)
2. [用户界面设计](#32-用户界面设计)
3. [用例设计](#33-用例设计)
4. [类设计](#34-类设计)
5. [数据设计](#35-数据设计)
6. [部署设计](#36-部署设计)
## 1、引言
### 1.1 编写目的
本文档旨在详细说明智能战场医疗后送系统的软件设计规格,为开发团队提供系统实现的技术指导,同时为测试和维护团队提供系统结构的清晰描述。文档描述了系统的架构设计、模块功能、接口规范、数据结构等内容,确保系统的开发符合预期目标和质量要求。
### 1.2 读者对象
本文档的读者对象包括:
- 项目经理:了解系统总体设计和进度安排
- 系统开发人员:理解系统架构和详细设计,指导编码实现
- 测试人员:了解系统功能和接口,制定测试计划和用例
- 维护人员:了解系统结构,为后期维护提供参考
- 项目干系人:了解系统总体设计和主要功能
### 1.3 软件项目概述
**项目名称**:智能战场医疗后送系统
**项目简称**:医疗后送系统
**项目代号**MES-UAV-1.0
**用户单位**:军事医疗部门
**开发单位**:军事医疗装备研发中心
**项目需求描述**
- 功能需求系统基于无人机平台实现战场伤员位置标记、医疗物资运送、无人机状态监控等功能通过Web界面实现人机交互支持医疗后送任务的规划和执行。
- 性能需求系统需支持实时数据传输和处理无人机响应时间不超过3秒Web界面刷新率不低于5秒/次支持同时处理至少5个伤员请求和3个物资运送任务。
### 1.4 文档概述
本文档包含以下主要内容:
- 第1章介绍文档的编写目的、读者对象、项目概述等基本信息
- 第2章说明软件设计的目标、原则和约束条件
- 第3章详细描述软件设计包括体系结构、用户界面、用例、类、数据和部署设计
### 1.5 定义
| 术语/缩写 | 定义 |
|-----------|------|
| MES | Medical Evacuation System医疗后送系统 |
| UAV | Unmanned Aerial Vehicle无人机 |
| MAVLink | Micro Air Vehicle Link微型飞行器通信协议 |
| SITL | Software In The Loop软件在环模拟 |
| GCS | Ground Control Station地面控制站 |
| API | Application Programming Interface应用程序接口 |
| GPS | Global Positioning System全球定位系统 |
| WebSocket | 一种在单个TCP连接上进行全双工通信的协议 |
### 1.6 参考资料
1. 《无人机作战应用技术规范》军事医疗装备研究所2022年
2. 《战场医疗救援系统需求分析报告》军事医疗部门2022年
3. DroneKit-Python API文档https://dronekit-python.readthedocs.io/
4. MAVLink协议规范https://mavlink.io/en/
5. Flask Web框架文档https://flask.palletsprojects.com/
6. Socket.IO实时应用框架文档https://socket.io/docs/
## 2、软件设计约束
### 2.1 软件设计目标和原则
**设计目标**
1. 实现用户需求:满足战场医疗后送的实际需求,提供伤员位置标记、医疗物资运送、任务规划等功能
2. 系统可靠性:确保系统在战场环境中稳定运行,具备一定的容错和恢复能力
3. 易用性:提供简单直观的用户界面,减少操作人员的培训成本
4. 可扩展性:系统架构支持功能模块的扩展和新型无人机的接入
5. 可维护性:采用模块化设计,便于系统维护和升级
**设计原则**
1. 模块化原则:系统功能划分为独立模块,降低模块间耦合度
2. 开闭原则:系统设计对扩展开放,对修改关闭
3. 单一职责原则:每个类和模块职责单一,提高内聚性
4. 接口隔离原则:定义清晰的模块接口,降低依赖
5. 依赖倒置原则:高层模块不应依赖低层模块,都应依赖抽象
6. 抽象设计原则:抽象出核心概念和通用接口,支持不同实现
### 2.2 软件设计的约束和限制
**运行环境要求**
- 硬件平台支持x86/64架构的服务器或边缘计算设备
- 操作系统Linux推荐Ubuntu 20.04 LTS或Windows 10/11
**开发语言**
- 后端Python 3.8+
- 前端HTML5, CSS3, JavaScript (Vue.js框架)
**标准规范**
- 代码规范PEP 8 (Python)
- API设计RESTful风格
- 通信协议MAVLink (无人机通信)WebSocket (实时数据传输)
**开发工具**
- 集成开发环境PyCharm或Visual Studio Code
- 版本控制Git
- 测试工具Pytest
- 文档工具Sphinx
**容量和性能要求**
- 支持同时处理不少于5个无人机连接
- Web界面响应时间不超过1秒
- 无人机命令响应时间不超过3秒
- 系统连续运行时间不少于72小时
**灵活性和配置要求**
- 通过配置文件支持不同环境(开发、测试、生产)配置
- 支持不同类型无人机的适配
- 支持模拟和实际操作模式切换
- 支持本地部署和云部署方式
## 3、软件设计
### 3.1 软件体系结构设计
智能战场医疗后送系统采用分层模块化架构,从逻辑上分为以下几个主要层次:
**1. 表示层**
- Web界面模块提供基于Web的用户交互界面
- 实时数据可视化模块:显示无人机状态和任务进度
**2. 应用层**
- 任务管理模块:处理任务创建、分配和监控
- 通信管理模块:处理外部系统通信请求
**3. 领域层**
- 无人机控制模块:负责无人机的基本操作控制
- 任务规划模块:制定无人机执行任务的路径规划
- 位置管理模块:管理伤员和基地位置信息
- 医疗物资模块:管理医疗物资库存和请求
**4. 基础设施层**
- 配置管理模块:处理系统配置和环境设置
- 数据持久化模块:存储系统运行数据
- 日志模块:记录系统运行日志
**架构图**
```
+-------------------+ +-------------------+
| Web界面模块 | | 实时数据可视化模块 |
+-------------------+ +-------------------+
| |
v v
+-------------------+ +-------------------+
| 任务管理模块 | | 通信管理模块 |
+-------------------+ +-------------------+
| | |
v v v
+-------------------+ +-------------------+ +-------------------+
| 无人机控制模块 | | 任务规划模块 | | 位置管理模块 |
+-------------------+ +-------------------+ +-------------------+
| | |
v v v
+-------------------+ +-------------------+ +-------------------+
| 医疗物资模块 | | 配置管理模块 | | 日志模块 |
+-------------------+ +-------------------+ +-------------------+
|
v
+-------------------+
| 数据持久化模块 |
+-------------------+
```
**模块说明**
1. **Web界面模块WebInterface**
- 负责提供基于Web的用户交互界面
- 使用Flask框架和Socket.IO实现
- 支持伤员位置标记、物资请求、无人机状态监控等功能
2. **任务管理模块MissionPlanner**
- 负责创建、安排和监控任务
- 定义任务类型(伤员转运、物资运送)
- 管理任务队列和优先级
3. **无人机控制模块DroneController**
- 负责与无人机建立连接和通信
- 执行基本飞行控制(起飞、降落、导航等)
- 监控无人机状态(位置、电量等)
4. **位置管理模块PositionManager**
- 管理伤员位置信息
- 提供位置查询和距离计算功能
- 支持地理编码功能
5. **医疗物资模块MedicalSupplyManager**
- 管理医疗物资库存
- 处理物资请求和分配
- 跟踪物资使用情况
6. **通信管理模块CommunicationManager**
- 处理与外部系统的通信
- 接收外部伤员信息和物资请求
- 支持多种通信协议
7. **配置管理模块Config**
- 管理系统配置参数
- 支持不同环境下的配置切换
- 提供配置加载和验证功能
### 3.2 用户界面设计
系统采用Web界面设计主要包含以下界面组件
**1. 主控制面板**
- 显示系统概览和无人机状态
- 包含任务列表和执行状态
- 提供快速操作按钮
**2. 地图视图**
- 显示无人机、伤员和基地位置
- 支持地图缩放和平移
- 允许直接在地图上标记伤员位置
**3. 物资管理界面**
- 显示可用医疗物资列表
- 物资库存状态和使用记录
- 创建物资运送请求
**4. 任务规划界面**
- 创建和编辑任务
- 显示任务执行路径
- 任务优先级设置
**5. 系统设置界面**
- 系统参数配置
- 无人机连接设置
- 用户权限管理
**界面跳转关系**
```
+----------------+
| 登录界面 |
+----------------+
|
v
+----------------+
| 主控制面板 |
+----------------+
| | | |
v v v v
+------+ +------+ +------+ +------+
|地图 | |物资 | |任务 | |系统 |
|视图 | |管理 | |规划 | |设置 |
+------+ +------+ +------+ +------+
```
### 3.3 用例设计
系统主要用例设计如下:
**1. 标记伤员位置**
顺序图:
```
操作员 -> Web界面: 在地图上标记伤员位置
Web界面 -> PositionManager: 添加伤员位置(casualty_id, lat, lon)
PositionManager -> PositionManager: 存储伤员位置信息
PositionManager -> Web界面: 返回成功状态
Web界面 -> 操作员: 显示伤员标记成功
```
**2. 请求医疗物资运送**
顺序图:
```
操作员 -> Web界面: 创建物资运送请求
Web界面 -> MedicalSupplyManager: 创建物资请求(target_id, supply_id, quantity)
MedicalSupplyManager -> MedicalSupplyManager: 检查库存可用性
MedicalSupplyManager -> PositionManager: 获取目标位置(target_id)
PositionManager -> MedicalSupplyManager: 返回目标位置
MedicalSupplyManager -> MissionPlanner: 创建物资运送任务(mission_id, target_location)
MissionPlanner -> MissionPlanner: 将任务添加到队列
MissionPlanner -> MedicalSupplyManager: 返回任务创建状态
MedicalSupplyManager -> Web界面: 返回请求状态
Web界面 -> 操作员: 显示请求结果
```
**3. 执行无人机任务**
顺序图:
```
MissionPlanner -> MissionPlanner: 从队列获取下一个任务
MissionPlanner -> DroneController: 执行起飞(altitude)
DroneController -> DroneController: 控制无人机起飞
DroneController -> MissionPlanner: 返回起飞状态
MissionPlanner -> DroneController: 导航到目标位置(lat, lon, altitude)
DroneController -> DroneController: 控制无人机飞往目标位置
DroneController -> MissionPlanner: 返回导航状态
MissionPlanner -> DroneController: 执行降落
DroneController -> DroneController: 控制无人机降落
DroneController -> MissionPlanner: 返回降落状态
MissionPlanner -> MissionPlanner: 更新任务状态为完成
```
**4. 监控无人机状态**
顺序图:
```
操作员 -> Web界面: 请求无人机状态
Web界面 -> DroneController: 获取无人机状态
DroneController -> Web界面: 返回状态数据(位置,电量,任务)
Web界面 -> 操作员: 显示无人机状态
```
### 3.4 类设计
系统的主要类设计如下:
**1. MedicalEvacuationSystem主系统类**
- 属性:
- drone_controller: DroneController
- mission_planner: MissionPlanner
- position_manager: PositionManager
- medical_supply_manager: MedicalSupplyManager
- communication_manager: CommunicationManager
- web_interface: WebInterface
- 方法:
- __init__(): 初始化系统
- start(): 启动系统
- stop(): 停止系统
- _handle_casualty_request(data): 处理伤员请求
- _handle_supply_request(data): 处理物资请求
- _handle_drone_status_request(): 处理无人机状态请求
**2. DroneController无人机控制类**
- 属性:
- vehicle: Vehicle
- connection_string: string
- logger: Logger
- 方法:
- connect(): 连接到无人机
- arm_and_takeoff(target_altitude): 解锁并起飞
- goto_location(lat, lon, altitude): 飞往指定位置
- land(): 降落
- close(): 关闭连接
**3. Mission任务类**
- 属性:
- mission_id: string
- mission_type: MissionType
- target_location: tuple(lat, lon)
- altitude: float
- payload: dict
- status: MissionStatus
- start_time: datetime
- end_time: datetime
- 方法:
- to_dict(): 转换为字典表示
**4. MissionPlanner任务规划类**
- 属性:
- drone_controller: DroneController
- mission_queue: Queue
- current_mission: Mission
- mission_history: list
- running: bool
- 方法:
- start(): 启动任务规划器
- stop(): 停止任务规划器
- add_mission(mission): 添加任务
- get_current_mission(): 获取当前任务
- get_mission_history(): 获取任务历史
**5. PositionManager位置管理类**
- 属性:
- casualty_positions: dict
- geocoder: Geocoder
- 方法:
- add_casualty_position(casualty_id, lat, lon): 添加伤员位置
- get_casualty_position(casualty_id): 获取伤员位置
- calculate_distance(lat1, lon1, lat2, lon2): 计算距离
- get_nearest_casualty(lat, lon): 获取最近伤员
**6. MedicalSupply医疗物资类**
- 属性:
- id: string
- name: string
- description: string
- weight: float
- quantity: int
**7. MedicalSupplyManager医疗物资管理类**
- 属性:
- supplies: dict
- supply_requests: dict
- 方法:
- get_all_supplies(): 获取所有物资
- create_supply_request(target_id, supply_id, quantity): 创建物资请求
- update_request_status(request_id, status): 更新请求状态
**8. WebInterfaceWeb界面类**
- 属性:
- app: Flask
- socketio: SocketIO
- casualty_handler: function
- supply_handler: function
- drone_status_handler: function
- 方法:
- start(): 启动Web服务
- broadcast_casualty_update(data): 广播伤员更新
- broadcast_drone_update(data): 广播无人机状态更新
**9. CommunicationManager通信管理类**
- 属性:
- server_socket: Socket
- clients: list
- casualty_handler: function
- supply_handler: function
- 方法:
- start_server(): 启动通信服务器
- close(): 关闭服务器
- send_message(client, message): 发送消息
### 3.5 数据设计
系统主要使用内存数据结构存储运行时数据,同时提供数据持久化功能。
**1. 内存数据结构**
- **伤员位置数据**
```python
casualty_positions = {
'casualty_id': {
'latitude': float,
'longitude': float,
'timestamp': float,
'location_description': string
}
}
```
- **医疗物资数据**
```python
supplies = {
'supply_id': MedicalSupply(
id='supply_id',
name='name',
description='description',
weight=float,
quantity=int
)
}
```
- **物资请求数据**
```python
supply_requests = {
'request_id': {
'request_id': string,
'target_id': string,
'supply_id': string,
'quantity': int,
'status': string,
'timestamp': float
}
}
```
- **任务数据**
```python
mission = {
'mission_id': string,
'mission_type': string,
'target_location': (lat, lon),
'altitude': float,
'payload': dict,
'status': string,
'start_time': datetime,
'end_time': datetime,
'error_message': string
}
```
**2. 数据持久化**
系统将支持以下数据持久化机制:
- **日志文件**:记录系统运行日志和关键事件
- **配置文件**:存储系统配置参数
- **数据导出**支持将任务历史、伤员位置等数据导出为JSON或CSV格式
### 3.6 部署设计
系统支持以下部署方式:
**1. 单机部署**
```
+-----------------+
| 部署服务器 |
+-----------------+
| Web服务 (Flask)|
| 通信服务 |
| 应用逻辑 |
+-----------------+
|
v
+-----------------+
| 无人机 |
+-----------------+
```
- 所有组件部署在同一台服务器上
- 适合小规模应用场景
- 硬件要求4核CPU8GB内存50GB存储空间
**2. 分布式部署**
```
+-----------------+ +-----------------+
| Web服务器 | | 应用服务器 |
+-----------------+ +-----------------+
| Web界面 | | 任务规划 |
| Socket.IO |<-->| 无人机控制 |
+-----------------+ | 位置管理 |
| 物资管理 |
+-----------------+
|
v
+-----------------+
| 无人机 |
+-----------------+
```
- Web服务与应用逻辑分离部署
- 适合中大规模应用或高负载场景
- 支持水平扩展,提高系统可用性
**3. 边缘计算部署**
```
+----------------+ +----------------+
| 云端服务器 | | 边缘服务器 |
+----------------+ +----------------+
| Web界面 | | 任务规划 |
| 数据存储 |<-->| 无人机控制 |
| 分析报表 | | 通信服务 |
+----------------+ +----------------+
|
v
+----------------+
| 无人机 |
+----------------+
```
- 核心控制逻辑部署在靠近作战区域的边缘服务器
- Web界面和数据分析功能部署在云端
- 提高控制响应速度,降低网络依赖
- 适合网络条件受限的战场环境
**部署要求**
- 操作系统Ubuntu 20.04 LTS
- Python 3.8+及相关依赖包
- 网络支持TCP/IP通信提供稳定的网络连接
- 安全:配置防火墙规则,保护关键服务端口
- 存储:提供足够的日志和数据存储空间
Loading…
Cancel
Save