Upload 23 files
Browse files- .gitattributes +1 -0
- AirDroneClient.py +336 -0
- AirDroneClient.pyproj +51 -0
- AirDroneClient.pyproj.user +6 -0
- AirDroneClient.sln +23 -0
- CarController.py +80 -0
- DroneController.py +256 -0
- FileConverter.py +27 -0
- LICENSE +201 -0
- SeqOutputer.py +53 -0
- airsim/__init__.py +5 -0
- airsim/__pycache__/__init__.cpython-39.pyc +0 -0
- airsim/__pycache__/client.cpython-39.pyc +0 -0
- airsim/__pycache__/types.cpython-39.pyc +0 -0
- airsim/__pycache__/utils.cpython-39.pyc +0 -0
- airsim/client.py +1621 -0
- airsim/pfm.py +85 -0
- airsim/types.py +580 -0
- airsim/utils.py +208 -0
- create_ir_segmentation_map.py +213 -0
- pic/image 1.png +3 -0
- pic/image 2.png +0 -0
- pic/image 3.png +0 -0
- pic/image_1.jpg +0 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
pic/image[[:space:]]1.png filter=lfs diff=lfs merge=lfs -text
|
AirDroneClient.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import sys
|
| 2 |
+
from dataclasses import dataclass
|
| 3 |
+
|
| 4 |
+
from PyQt5.QtCore import *
|
| 5 |
+
from PyQt5.QtWidgets import *
|
| 6 |
+
import keyboard
|
| 7 |
+
|
| 8 |
+
import DroneController # 假设只需要无人机控制
|
| 9 |
+
|
| 10 |
+
# mobile configuration
|
| 11 |
+
@dataclass
|
| 12 |
+
class Vehicle():
|
| 13 |
+
acc = 1
|
| 14 |
+
vehicle_type = 'drone' # 设置为无人机模式
|
| 15 |
+
|
| 16 |
+
veh = Vehicle()
|
| 17 |
+
|
| 18 |
+
# form application #
|
| 19 |
+
app = QApplication(sys.argv)
|
| 20 |
+
|
| 21 |
+
class AirDroneClientWindow(QWidget):
|
| 22 |
+
button_takeoff = QPushButton()
|
| 23 |
+
button_land = QPushButton()
|
| 24 |
+
button_up = QPushButton()
|
| 25 |
+
button_down = QPushButton()
|
| 26 |
+
|
| 27 |
+
button_f = QPushButton()
|
| 28 |
+
button_b = QPushButton()
|
| 29 |
+
button_l = QPushButton()
|
| 30 |
+
button_r = QPushButton()
|
| 31 |
+
|
| 32 |
+
button_weather = QPushButton()
|
| 33 |
+
button_task = QPushButton()
|
| 34 |
+
button_algorithm = QPushButton()
|
| 35 |
+
button_formation = QPushButton()
|
| 36 |
+
button_keyboard = QPushButton()
|
| 37 |
+
|
| 38 |
+
weather_selection = QComboBox()
|
| 39 |
+
|
| 40 |
+
label_gps_alt = QLabel()
|
| 41 |
+
label_gps_lat = QLabel()
|
| 42 |
+
label_gps_lon = QLabel()
|
| 43 |
+
label_rot_w = QLabel()
|
| 44 |
+
label_rot_y = QLabel()
|
| 45 |
+
label_rot_z = QLabel()
|
| 46 |
+
label_speed = QLabel()
|
| 47 |
+
label_angle_speed = QLabel()
|
| 48 |
+
|
| 49 |
+
refreshinfo = QTimer()
|
| 50 |
+
|
| 51 |
+
def initUI(self):
|
| 52 |
+
self.resize(500, 400) # 调整为更合适的尺寸
|
| 53 |
+
self.setWindowTitle("U21Data-2 Client")
|
| 54 |
+
|
| 55 |
+
#self.set_background()
|
| 56 |
+
|
| 57 |
+
self.setObjectName("AirDroneClientWindow") # 设置对象名称匹配选择器
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
self.setStyleSheet(self.styleSheet())
|
| 61 |
+
|
| 62 |
+
# 主垂直布局
|
| 63 |
+
main_layout = QVBoxLayout()
|
| 64 |
+
|
| 65 |
+
# 添加Start Point部分
|
| 66 |
+
start_group = QGroupBox("Start Point")
|
| 67 |
+
start_layout = QHBoxLayout()
|
| 68 |
+
|
| 69 |
+
self.start_combo = QComboBox()
|
| 70 |
+
self.start_combo.setEditable(True)
|
| 71 |
+
self.start_combo.lineEdit().setReadOnly(True)
|
| 72 |
+
self.start_combo.lineEdit().setAlignment(Qt.AlignCenter)
|
| 73 |
+
self.start_combo.addItems(["9.8, -279, -22", "213, -429, 19","-315, -444, 12", "-335, 282, 19", "-152, 236, 66", "-430, -91, 13"])
|
| 74 |
+
self.start_combo.setCurrentIndex(-1)
|
| 75 |
+
self.start_combo.lineEdit().setPlaceholderText("StartPoint")
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
self.weather_selection = QComboBox()
|
| 79 |
+
self.weather_selection.setEditable(True)
|
| 80 |
+
self.weather_selection.lineEdit().setReadOnly(True)
|
| 81 |
+
self.weather_selection.lineEdit().setAlignment(Qt.AlignCenter)
|
| 82 |
+
self.weather_selection.addItems(["Clear", "Rain", "Snow", "Fog", "Overcast","Sand"])
|
| 83 |
+
self.weather_selection.setCurrentIndex(-1)
|
| 84 |
+
self.weather_selection.lineEdit().setPlaceholderText("Weather")
|
| 85 |
+
|
| 86 |
+
for widget in [self.start_combo, self.weather_selection]:
|
| 87 |
+
widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
| 88 |
+
|
| 89 |
+
start_layout.addWidget(self.start_combo)
|
| 90 |
+
start_layout.addWidget(self.weather_selection)
|
| 91 |
+
start_group.setLayout(start_layout)
|
| 92 |
+
|
| 93 |
+
# 添加功能按钮部分
|
| 94 |
+
func_group = QGroupBox("Functions")
|
| 95 |
+
func_layout = QHBoxLayout()
|
| 96 |
+
|
| 97 |
+
self.button_keyboard = QComboBox()
|
| 98 |
+
self.button_keyboard.setEditable(True)
|
| 99 |
+
self.button_keyboard.lineEdit().setReadOnly(True)
|
| 100 |
+
self.button_keyboard.lineEdit().setAlignment(Qt.AlignCenter)
|
| 101 |
+
self.button_keyboard.addItems(["Yes", "No"])
|
| 102 |
+
self.button_keyboard.setCurrentIndex(-1)
|
| 103 |
+
self.button_keyboard.lineEdit().setPlaceholderText("Task")
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
#Task
|
| 108 |
+
self.task_combo = QComboBox()
|
| 109 |
+
self.task_combo.setEditable(True) # 设置为可编辑
|
| 110 |
+
self.task_combo.lineEdit().setReadOnly(True) # 设置行编辑为只读
|
| 111 |
+
self.task_combo.lineEdit().setAlignment(Qt.AlignCenter) # 文字居中
|
| 112 |
+
self.task_combo.addItems(["Task 1", "Task 2", "Task 3"])
|
| 113 |
+
self.task_combo.setCurrentIndex(-1) # 不选中任何项
|
| 114 |
+
self.task_combo.lineEdit().setPlaceholderText("Task")
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
# 设置Algorithm下拉框选项
|
| 118 |
+
self.algorithm_combo = QComboBox()
|
| 119 |
+
self.algorithm_combo.setEditable(True) # 设置为可编辑
|
| 120 |
+
self.algorithm_combo.lineEdit().setReadOnly(True) # 设置行编辑为只读
|
| 121 |
+
self.algorithm_combo.lineEdit().setAlignment(Qt.AlignCenter) # 文字居中
|
| 122 |
+
self.algorithm_combo.addItems(["Algorithm 1", "Algorithm 2", "Algorithm 3"])
|
| 123 |
+
self.algorithm_combo.setCurrentIndex(-1) # 不选中任何项
|
| 124 |
+
self.algorithm_combo.lineEdit().setPlaceholderText("Algorithm") # 设置占位文本
|
| 125 |
+
|
| 126 |
+
# 设置Formation下拉框选项
|
| 127 |
+
self.formation_combo = QComboBox()
|
| 128 |
+
self.formation_combo.setEditable(True)
|
| 129 |
+
self.formation_combo.lineEdit().setReadOnly(True)
|
| 130 |
+
self.formation_combo.lineEdit().setAlignment(Qt.AlignCenter)
|
| 131 |
+
self.formation_combo.addItems(["Line Formation", "V Formation", "Square Formation", "Circle Formation"])
|
| 132 |
+
self.formation_combo.setCurrentIndex(-1)
|
| 133 |
+
self.formation_combo.lineEdit().setPlaceholderText("Formation")
|
| 134 |
+
|
| 135 |
+
# 设置控件大小策略
|
| 136 |
+
for widget in [self.task_combo, self.algorithm_combo,
|
| 137 |
+
self.formation_combo, self.button_keyboard]:
|
| 138 |
+
widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
| 139 |
+
|
| 140 |
+
# 添加控件到布局
|
| 141 |
+
func_layout.addWidget(self.task_combo)
|
| 142 |
+
func_layout.addWidget(self.algorithm_combo)
|
| 143 |
+
func_layout.addWidget(self.formation_combo)
|
| 144 |
+
func_layout.addWidget(self.button_keyboard)
|
| 145 |
+
func_group.setLayout(func_layout)
|
| 146 |
+
|
| 147 |
+
# 添加飞行控制部分
|
| 148 |
+
flight_group = QGroupBox("Flight Controls")
|
| 149 |
+
flight_layout = QVBoxLayout()
|
| 150 |
+
|
| 151 |
+
# 将Takeoff, Land, Up, Down四个按钮放在同一行
|
| 152 |
+
top_row_layout = QHBoxLayout()
|
| 153 |
+
self.button_takeoff = QPushButton("Takeoff")
|
| 154 |
+
self.button_land = QPushButton("Land")
|
| 155 |
+
self.button_up = QPushButton("Up")
|
| 156 |
+
self.button_down = QPushButton("Down")
|
| 157 |
+
|
| 158 |
+
# 设置按钮大小策略,使它们均匀分布
|
| 159 |
+
for button in [self.button_takeoff, self.button_land,
|
| 160 |
+
self.button_up, self.button_down]:
|
| 161 |
+
button.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
| 162 |
+
|
| 163 |
+
top_row_layout.addWidget(self.button_takeoff)
|
| 164 |
+
top_row_layout.addWidget(self.button_land)
|
| 165 |
+
top_row_layout.addWidget(self.button_up)
|
| 166 |
+
top_row_layout.addWidget(self.button_down)
|
| 167 |
+
top_row_layout.setSpacing(5) # 设置按钮间距
|
| 168 |
+
|
| 169 |
+
# 前后左右
|
| 170 |
+
move_layout = QGridLayout()
|
| 171 |
+
self.button_f = QPushButton("Move Forward")
|
| 172 |
+
self.button_b = QPushButton("Move Backward")
|
| 173 |
+
self.button_l = QPushButton("Move Left")
|
| 174 |
+
self.button_r = QPushButton("Move Right")
|
| 175 |
+
move_layout.addWidget(self.button_f, 0, 1)
|
| 176 |
+
move_layout.addWidget(self.button_b, 2, 1)
|
| 177 |
+
move_layout.addWidget(self.button_l, 1, 0)
|
| 178 |
+
move_layout.addWidget(self.button_r, 1, 2)
|
| 179 |
+
|
| 180 |
+
flight_layout.addLayout(top_row_layout)
|
| 181 |
+
flight_layout.addLayout(move_layout)
|
| 182 |
+
flight_group.setLayout(flight_layout)
|
| 183 |
+
|
| 184 |
+
# 添加日志信息部分
|
| 185 |
+
log_group = QGroupBox("Log")
|
| 186 |
+
log_layout = QVBoxLayout()
|
| 187 |
+
self.label_gps_alt = QLabel("GPS Altitude: ")
|
| 188 |
+
self.label_gps_lat = QLabel("GPS Latitude: ")
|
| 189 |
+
self.label_gps_lon = QLabel("GPS Longitude: ")
|
| 190 |
+
self.label_rot_w = QLabel("Rotation w: ")
|
| 191 |
+
self.label_rot_y = QLabel("Rotation y: ")
|
| 192 |
+
self.label_rot_z = QLabel("Rotation z: ")
|
| 193 |
+
self.label_speed = QLabel("Linear Speed: ")
|
| 194 |
+
self.label_angle_speed = QLabel("Angle Speed: ")
|
| 195 |
+
|
| 196 |
+
log_layout.addWidget(self.label_gps_alt)
|
| 197 |
+
log_layout.addWidget(self.label_gps_lat)
|
| 198 |
+
log_layout.addWidget(self.label_gps_lon)
|
| 199 |
+
log_layout.addWidget(self.label_rot_w)
|
| 200 |
+
log_layout.addWidget(self.label_rot_y)
|
| 201 |
+
log_layout.addWidget(self.label_rot_z)
|
| 202 |
+
log_layout.addWidget(self.label_speed)
|
| 203 |
+
log_layout.addWidget(self.label_angle_speed)
|
| 204 |
+
log_group.setLayout(log_layout)
|
| 205 |
+
|
| 206 |
+
# 将所有组添加到主布局
|
| 207 |
+
main_layout.addWidget(start_group)
|
| 208 |
+
main_layout.addWidget(func_group)
|
| 209 |
+
main_layout.addWidget(flight_group)
|
| 210 |
+
main_layout.addWidget(log_group)
|
| 211 |
+
|
| 212 |
+
self.setLayout(main_layout)
|
| 213 |
+
|
| 214 |
+
# 连接信号槽
|
| 215 |
+
self.button_takeoff.clicked.connect(DroneController.dronecontrol.TakeOff)
|
| 216 |
+
self.button_land.clicked.connect(DroneController.dronecontrol.Landed)
|
| 217 |
+
self.button_up.clicked.connect(self.up)
|
| 218 |
+
self.button_down.clicked.connect(self.down)
|
| 219 |
+
self.button_f.pressed.connect(self.forward)
|
| 220 |
+
self.button_b.pressed.connect(self.backward)
|
| 221 |
+
self.button_l.pressed.connect(self.left)
|
| 222 |
+
self.button_r.pressed.connect(self.right)
|
| 223 |
+
self.button_f.released.connect(self.stop)
|
| 224 |
+
self.button_b.released.connect(self.stop)
|
| 225 |
+
self.button_l.released.connect(self.stop)
|
| 226 |
+
self.button_r.released.connect(self.stop)
|
| 227 |
+
self.button_keyboard.currentIndexChanged.connect(self.EnableKeyboard)
|
| 228 |
+
self.weather_selection.currentIndexChanged.connect(self.change_weather)
|
| 229 |
+
self.algorithm_combo.currentIndexChanged.connect(self.on_algorithm_changed)
|
| 230 |
+
self.formation_combo.currentIndexChanged.connect(self.on_formation_changed)
|
| 231 |
+
self.task_combo.currentIndexChanged.connect(self.on_task_changed)
|
| 232 |
+
self.start_combo.currentIndexChanged.connect(self.on_start_changed)
|
| 233 |
+
|
| 234 |
+
# 自动刷新信息
|
| 235 |
+
self.refreshinfo.start(1000)
|
| 236 |
+
self.refreshinfo.timeout.connect(self.setInfo)
|
| 237 |
+
|
| 238 |
+
self.show()
|
| 239 |
+
|
| 240 |
+
def setInfo(self):
|
| 241 |
+
self.label_gps_alt.setText("GPS Altitude: " + str(DroneController.dronecontrol.GetState().gps_pos_altitude))
|
| 242 |
+
self.label_gps_lat.setText("GPS Latitude: " + str(DroneController.dronecontrol.GetState().gps_pos_latitude))
|
| 243 |
+
self.label_gps_lon.setText("GPS Longitude: " + str(DroneController.dronecontrol.GetState().gps_pos_longitude))
|
| 244 |
+
|
| 245 |
+
self.label_rot_w.setText("Rotation w: " + str(DroneController.dronecontrol.GetState().ori_x))
|
| 246 |
+
self.label_rot_y.setText("Rotation y: " + str(DroneController.dronecontrol.GetState().ori_y))
|
| 247 |
+
self.label_rot_z.setText("Rotation z: " + str(DroneController.dronecontrol.GetState().ori_z))
|
| 248 |
+
|
| 249 |
+
self.label_speed.setText("Linear Speed: " + str(DroneController.dronecontrol.GetState().linear_speed))
|
| 250 |
+
self.label_angle_speed.setText("Angle Speed: " + str(DroneController.dronecontrol.GetState().a_v_x))
|
| 251 |
+
|
| 252 |
+
# 移动功能
|
| 253 |
+
def forward(self):
|
| 254 |
+
DroneController.dronecontrol.DroneMoveByTime(veh.acc, 0, 0)
|
| 255 |
+
|
| 256 |
+
def backward(self):
|
| 257 |
+
DroneController.dronecontrol.DroneMoveByTime(-veh.acc, 0, 0)
|
| 258 |
+
|
| 259 |
+
def left(self):
|
| 260 |
+
DroneController.dronecontrol.DroneMoveByTime(0, -veh.acc, 0)
|
| 261 |
+
|
| 262 |
+
def right(self):
|
| 263 |
+
DroneController.dronecontrol.DroneMoveByTime(0, veh.acc, 0)
|
| 264 |
+
|
| 265 |
+
def stop(self):
|
| 266 |
+
pass
|
| 267 |
+
|
| 268 |
+
def up(self):
|
| 269 |
+
DroneController.dronecontrol.DroneMoveByTime(0, 0, -veh.acc)
|
| 270 |
+
|
| 271 |
+
def down(self):
|
| 272 |
+
DroneController.dronecontrol.DroneMoveByTime(0, 0, veh.acc)
|
| 273 |
+
|
| 274 |
+
def change_weather(self, index):
|
| 275 |
+
if index >= 0:
|
| 276 |
+
weather = self.weather_selection.currentText()
|
| 277 |
+
if weather == "Snow":
|
| 278 |
+
DroneController.dronecontrol.SnowTeleport()
|
| 279 |
+
elif weather == "Rain":
|
| 280 |
+
DroneController.dronecontrol.RainTeleport()
|
| 281 |
+
elif weather == "Fog":
|
| 282 |
+
DroneController.dronecontrol.FogTeleport()
|
| 283 |
+
elif weather == "Overcast":
|
| 284 |
+
DroneController.dronecontrol.OvercastTeleport()
|
| 285 |
+
elif weather == "Clear":
|
| 286 |
+
DroneController.dronecontrol.ClearTeleport()
|
| 287 |
+
elif weather == "Sand":
|
| 288 |
+
DroneController.dronecontrol.SandTeleport()
|
| 289 |
+
|
| 290 |
+
def on_algorithm_changed(self, index):
|
| 291 |
+
"""Algorithm下拉框选择变化时的处理"""
|
| 292 |
+
if index >= 0:
|
| 293 |
+
selected_algorithm = self.algorithm_combo.currentText()
|
| 294 |
+
print(f"Selected Algorithm: {selected_algorithm}")
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
def on_formation_changed(self, index):
|
| 298 |
+
"""Formation下拉框选择变化时的处理"""
|
| 299 |
+
if index >= 0:
|
| 300 |
+
selected_formation = self.formation_combo.currentText()
|
| 301 |
+
print(f"Selected Formation: {selected_formation}")
|
| 302 |
+
|
| 303 |
+
|
| 304 |
+
def on_task_changed(self, index):
|
| 305 |
+
"""Task下拉框选择变化时的处理"""
|
| 306 |
+
if index >= 0:
|
| 307 |
+
selected_task = self.task_combo.currentText()
|
| 308 |
+
print(f"Selected Task: {selected_task}")
|
| 309 |
+
|
| 310 |
+
|
| 311 |
+
def on_start_changed(self, index):
|
| 312 |
+
if index >= 0:
|
| 313 |
+
pos = self.start_combo.currentText()
|
| 314 |
+
if pos == "9.8, -279, -22":
|
| 315 |
+
DroneController.dronecontrol.SnowTeleport()
|
| 316 |
+
elif pos == "213, -429, 19":
|
| 317 |
+
DroneController.dronecontrol.OvercastTeleport()
|
| 318 |
+
elif pos == "-315, -444, 12":
|
| 319 |
+
DroneController.dronecontrol.RainTeleport()
|
| 320 |
+
elif pos == "-335, 282, 19":
|
| 321 |
+
DroneController.dronecontrol.SandTeleport()
|
| 322 |
+
elif pos == "-152, 236, 66":
|
| 323 |
+
DroneController.dronecontrol.ClearTeleport()
|
| 324 |
+
elif pos == "-430, -91, 13":
|
| 325 |
+
DroneController.dronecontrol.FogTeleport()
|
| 326 |
+
else:
|
| 327 |
+
print(self.start_combo.currentIndex())
|
| 328 |
+
|
| 329 |
+
def EnableKeyboard(self):
|
| 330 |
+
keyboard.hook(DroneController.dronecontrol.KeyboardControl)
|
| 331 |
+
keyboard.wait()
|
| 332 |
+
|
| 333 |
+
window = AirDroneClientWindow()
|
| 334 |
+
window.initUI()
|
| 335 |
+
window.setInfo()
|
| 336 |
+
sys.exit(app.exec())
|
AirDroneClient.pyproj
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" ToolsVersion="4.0">
|
| 2 |
+
<PropertyGroup>
|
| 3 |
+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
| 4 |
+
<SchemaVersion>2.0</SchemaVersion>
|
| 5 |
+
<ProjectGuid>4e90b183-6758-4994-8334-cfa6a07c9508</ProjectGuid>
|
| 6 |
+
<ProjectHome>.</ProjectHome>
|
| 7 |
+
<StartupFile>DroneController.py</StartupFile>
|
| 8 |
+
<SearchPath>
|
| 9 |
+
</SearchPath>
|
| 10 |
+
<WorkingDirectory>.</WorkingDirectory>
|
| 11 |
+
<OutputPath>.</OutputPath>
|
| 12 |
+
<Name>AirDroneClient</Name>
|
| 13 |
+
<RootNamespace>AirDroneClient</RootNamespace>
|
| 14 |
+
<InterpreterId>MSBuild|env|$(MSBuildProjectFullPath)</InterpreterId>
|
| 15 |
+
</PropertyGroup>
|
| 16 |
+
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
|
| 17 |
+
<DebugSymbols>true</DebugSymbols>
|
| 18 |
+
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
| 19 |
+
</PropertyGroup>
|
| 20 |
+
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
| 21 |
+
<DebugSymbols>true</DebugSymbols>
|
| 22 |
+
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
| 23 |
+
</PropertyGroup>
|
| 24 |
+
<ItemGroup>
|
| 25 |
+
<Compile Include="AirDroneClient.py" />
|
| 26 |
+
<Compile Include="CarController.py" />
|
| 27 |
+
<Compile Include="DroneController.py" />
|
| 28 |
+
<Compile Include="FileConverter.py" />
|
| 29 |
+
<Compile Include="SeqOutputer.py" />
|
| 30 |
+
</ItemGroup>
|
| 31 |
+
<ItemGroup>
|
| 32 |
+
<Interpreter Include="env\">
|
| 33 |
+
<Id>env</Id>
|
| 34 |
+
<Version>3.11</Version>
|
| 35 |
+
<Description>env (Python 3.11 (64-bit))</Description>
|
| 36 |
+
<InterpreterPath>Scripts\python.exe</InterpreterPath>
|
| 37 |
+
<WindowsInterpreterPath>Scripts\pythonw.exe</WindowsInterpreterPath>
|
| 38 |
+
<PathEnvironmentVariable>PYTHONPATH</PathEnvironmentVariable>
|
| 39 |
+
<Architecture>X64</Architecture>
|
| 40 |
+
</Interpreter>
|
| 41 |
+
</ItemGroup>
|
| 42 |
+
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
|
| 43 |
+
<!-- Uncomment the CoreCompile target to enable the Build command in
|
| 44 |
+
Visual Studio and specify your pre- and post-build commands in
|
| 45 |
+
the BeforeBuild and AfterBuild targets below. -->
|
| 46 |
+
<!--<Target Name="CoreCompile" />-->
|
| 47 |
+
<Target Name="BeforeBuild">
|
| 48 |
+
</Target>
|
| 49 |
+
<Target Name="AfterBuild">
|
| 50 |
+
</Target>
|
| 51 |
+
</Project>
|
AirDroneClient.pyproj.user
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="utf-8"?>
|
| 2 |
+
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
| 3 |
+
<PropertyGroup>
|
| 4 |
+
<ProjectView>ProjectFiles</ProjectView>
|
| 5 |
+
</PropertyGroup>
|
| 6 |
+
</Project>
|
AirDroneClient.sln
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Microsoft Visual Studio Solution File, Format Version 12.00
|
| 3 |
+
# Visual Studio Version 17
|
| 4 |
+
VisualStudioVersion = 17.7.34202.233
|
| 5 |
+
MinimumVisualStudioVersion = 10.0.40219.1
|
| 6 |
+
Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "AirDroneClient", "AirDroneClient.pyproj", "{4E90B183-6758-4994-8334-CFA6A07C9508}"
|
| 7 |
+
EndProject
|
| 8 |
+
Global
|
| 9 |
+
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
| 10 |
+
Debug|Any CPU = Debug|Any CPU
|
| 11 |
+
Release|Any CPU = Release|Any CPU
|
| 12 |
+
EndGlobalSection
|
| 13 |
+
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
| 14 |
+
{4E90B183-6758-4994-8334-CFA6A07C9508}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
| 15 |
+
{4E90B183-6758-4994-8334-CFA6A07C9508}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
| 16 |
+
EndGlobalSection
|
| 17 |
+
GlobalSection(SolutionProperties) = preSolution
|
| 18 |
+
HideSolutionNode = FALSE
|
| 19 |
+
EndGlobalSection
|
| 20 |
+
GlobalSection(ExtensibilityGlobals) = postSolution
|
| 21 |
+
SolutionGuid = {65CCDCFD-026C-4C90-A618-CF4FBBFB37AD}
|
| 22 |
+
EndGlobalSection
|
| 23 |
+
EndGlobal
|
CarController.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from dataclasses import dataclass
|
| 2 |
+
import airsim
|
| 3 |
+
import time
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
@dataclass
|
| 7 |
+
class Carinfo():
|
| 8 |
+
speed_a = 0
|
| 9 |
+
speed_l = 0
|
| 10 |
+
|
| 11 |
+
pos_x = 0
|
| 12 |
+
pos_y = 0
|
| 13 |
+
pos_z = 0
|
| 14 |
+
|
| 15 |
+
rot_x = 0
|
| 16 |
+
rot_y = 0
|
| 17 |
+
rot_z = 0
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class CarController():
|
| 21 |
+
def __init__(self):
|
| 22 |
+
# connect to the AirSim simulator
|
| 23 |
+
self.client = airsim.CarClient()
|
| 24 |
+
self.client.confirmConnection()
|
| 25 |
+
self.client.enableApiControl(True)
|
| 26 |
+
self.car_controls = airsim.CarControls()
|
| 27 |
+
|
| 28 |
+
def GoForward(self, v):
|
| 29 |
+
self.car_controls.throttle = v
|
| 30 |
+
self.car_controls.steering = 0
|
| 31 |
+
self.client.setCarControls(self.car_controls)
|
| 32 |
+
|
| 33 |
+
def GoBackwardStart(self, v):
|
| 34 |
+
self.car_controls.throttle = v
|
| 35 |
+
self.car_controls.is_manual_gear = True
|
| 36 |
+
self.car_controls.manual_gear = -1
|
| 37 |
+
self.car_controls.steering = 0
|
| 38 |
+
self.client.setCarControls(self.car_controls)
|
| 39 |
+
|
| 40 |
+
def GoBackwardEnd(self):
|
| 41 |
+
self.car_controls.throttle = 0
|
| 42 |
+
self.car_controls.steering = 0
|
| 43 |
+
self.car_controls.is_manual_gear = False
|
| 44 |
+
self.car_controls.manual_gear = 0
|
| 45 |
+
self.client.setCarControls(self.car_controls)
|
| 46 |
+
|
| 47 |
+
def Steer(self, v, steering):
|
| 48 |
+
self.car_controls.throttle = v
|
| 49 |
+
self.car_controls.steering = steering
|
| 50 |
+
self.client.setCarControls(self.car_controls)
|
| 51 |
+
|
| 52 |
+
def Stop(self):
|
| 53 |
+
self.car_controls.throttle = 0
|
| 54 |
+
self.car_controls.steering = 0
|
| 55 |
+
self.client.setCarControls(self.car_controls)
|
| 56 |
+
|
| 57 |
+
def GetCarPose(self):
|
| 58 |
+
position = self.client.simGetVehiclePose().position
|
| 59 |
+
rotation = self.client.simGetVehiclePose().orientation
|
| 60 |
+
|
| 61 |
+
speed_a = self.client.getCarState().speed
|
| 62 |
+
speed_l = self.client.getCarState().speed
|
| 63 |
+
|
| 64 |
+
carinfo = Carinfo()
|
| 65 |
+
|
| 66 |
+
carinfo.rot_x = rotation.x_val
|
| 67 |
+
carinfo.rot_y = rotation.y_val
|
| 68 |
+
carinfo.rot_z = rotation.z_val
|
| 69 |
+
|
| 70 |
+
carinfo.pos_x = position.x_val
|
| 71 |
+
carinfo.pos_y = position.y_val
|
| 72 |
+
carinfo.pos_z = position.z_val
|
| 73 |
+
|
| 74 |
+
carinfo.speed_a = speed_a
|
| 75 |
+
carinfo.speed_l = speed_l
|
| 76 |
+
|
| 77 |
+
return carinfo
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
carcontrol = CarController()
|
DroneController.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from multiprocessing import Value
|
| 2 |
+
import airsim
|
| 3 |
+
import time
|
| 4 |
+
import math
|
| 5 |
+
import keyboard
|
| 6 |
+
import json
|
| 7 |
+
|
| 8 |
+
class DroneInfo():
|
| 9 |
+
gps_pos_altitude = 0
|
| 10 |
+
gps_pos_latitude = 0
|
| 11 |
+
gps_pos_longitude = 0
|
| 12 |
+
|
| 13 |
+
ori_w = 0
|
| 14 |
+
ori_x = 0
|
| 15 |
+
ori_y = 0
|
| 16 |
+
ori_z = 0
|
| 17 |
+
|
| 18 |
+
# linear_velocity
|
| 19 |
+
l_v_x = 0
|
| 20 |
+
l_v_y = 0
|
| 21 |
+
l_v_z = 0
|
| 22 |
+
|
| 23 |
+
# angular_velocity
|
| 24 |
+
a_v_x = 0
|
| 25 |
+
a_v_y = 0
|
| 26 |
+
a_v_z = 0
|
| 27 |
+
|
| 28 |
+
roll = 0
|
| 29 |
+
yaw = 0
|
| 30 |
+
pitch = 0
|
| 31 |
+
|
| 32 |
+
linear_speed = 0
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class Drone():
|
| 36 |
+
def __init__(self):
|
| 37 |
+
# connect to the AirSim simulator
|
| 38 |
+
self.client = airsim.MultirotorClient()
|
| 39 |
+
self.client.confirmConnection()
|
| 40 |
+
self.client.enableApiControl(True)
|
| 41 |
+
self.client.armDisarm(True)
|
| 42 |
+
|
| 43 |
+
def TakeOff(self):
|
| 44 |
+
if airsim.LandedState.Landed == 0:
|
| 45 |
+
self.client.takeoffAsync().join()
|
| 46 |
+
print("take off")
|
| 47 |
+
else:
|
| 48 |
+
self.client.hoverAsync().join()
|
| 49 |
+
print("already flying")
|
| 50 |
+
|
| 51 |
+
def Landed(self):
|
| 52 |
+
if airsim.LandedState.Landed == 1:
|
| 53 |
+
print("already landed")
|
| 54 |
+
else:
|
| 55 |
+
self.client.landAsync().join()
|
| 56 |
+
print("now landing")
|
| 57 |
+
|
| 58 |
+
def DroneMoveByTime(self, vx, vy, vz, duration=3):
|
| 59 |
+
print("now speed", vx, vy, vz)
|
| 60 |
+
self.client.moveByVelocityAsync(vx, vy, vz, duration)
|
| 61 |
+
time.sleep(duration)
|
| 62 |
+
print("End Moving")
|
| 63 |
+
|
| 64 |
+
def GetState(self):
|
| 65 |
+
# State = self.client.getMultirotorState()
|
| 66 |
+
State = DroneInfo()
|
| 67 |
+
gps_pos = self.client.getMultirotorState().gps_location
|
| 68 |
+
State.gps_pos_altitude = gps_pos.altitude
|
| 69 |
+
State.gps_pos_longitude = gps_pos.longitude
|
| 70 |
+
State.gps_pos_latitude = gps_pos.latitude
|
| 71 |
+
|
| 72 |
+
orientation = self.client.getMultirotorState().kinematics_estimated.orientation
|
| 73 |
+
State.ori_w = orientation.w_val
|
| 74 |
+
State.ori_x = orientation.x_val
|
| 75 |
+
State.ori_y = orientation.y_val
|
| 76 |
+
State.ori_z = orientation.z_val
|
| 77 |
+
|
| 78 |
+
a_velocity = self.client.getMultirotorState().kinematics_estimated.angular_velocity
|
| 79 |
+
State.a_v_x = a_velocity.x_val
|
| 80 |
+
State.a_v_y = a_velocity.y_val
|
| 81 |
+
State.a_v_z = a_velocity.z_val
|
| 82 |
+
|
| 83 |
+
l_velocity = self.client.getMultirotorState().kinematics_estimated.linear_velocity
|
| 84 |
+
State.l_v_x = l_velocity.x_val
|
| 85 |
+
State.l_v_y = l_velocity.y_val
|
| 86 |
+
State.l_v_z = l_velocity.z_val
|
| 87 |
+
|
| 88 |
+
State.roll = self.client.getMultirotorState().rc_data.roll
|
| 89 |
+
State.yaw = self.client.getMultirotorState().rc_data.yaw
|
| 90 |
+
State.pitch = self.client.getMultirotorState().rc_data.pitch
|
| 91 |
+
|
| 92 |
+
State.linear_speed = math.sqrt(pow(State.l_v_x,2)+pow(State.l_v_y,2)+pow(State.l_v_z,2))
|
| 93 |
+
|
| 94 |
+
return State
|
| 95 |
+
|
| 96 |
+
def Hover(self):
|
| 97 |
+
landed = self.client.getMultirotorState()
|
| 98 |
+
|
| 99 |
+
def MoveToPosition(self,x,y,z,duration=3):
|
| 100 |
+
self.client.moveToPositionAsync(x, y, z, duration).join()
|
| 101 |
+
|
| 102 |
+
def Reset(self):
|
| 103 |
+
self.client.reset()
|
| 104 |
+
|
| 105 |
+
def SnowTeleport(self):
|
| 106 |
+
position = airsim.Vector3r(9.87236677 , -279.41862836, -22.81082173) # snow XYZ 0.01 -Z
|
| 107 |
+
heading = airsim.utils.to_quaternion(0, 0, 0)
|
| 108 |
+
pose = airsim.Pose(position, heading)
|
| 109 |
+
self.client.simSetVehiclePose(pose, True)
|
| 110 |
+
|
| 111 |
+
def OvercastTeleport(self):
|
| 112 |
+
position = airsim.Vector3r(213.24547002 , -429.66393833, 19.260)
|
| 113 |
+
heading = airsim.utils.to_quaternion(0, 0, 0)
|
| 114 |
+
pose = airsim.Pose(position, heading)
|
| 115 |
+
self.client.simSetVehiclePose(pose, True)
|
| 116 |
+
|
| 117 |
+
def RainTeleport(self):
|
| 118 |
+
position = airsim.Vector3r(-315.200 , -444.920, 12.520)
|
| 119 |
+
heading = airsim.utils.to_quaternion(0, 0, 0)
|
| 120 |
+
pose = airsim.Pose(position, heading)
|
| 121 |
+
self.client.simSetVehiclePose(pose, True)
|
| 122 |
+
|
| 123 |
+
def SandTeleport(self):
|
| 124 |
+
position = airsim.Vector3r(-335.50020795 , 282.16650494, 19.50313758)
|
| 125 |
+
heading = airsim.utils.to_quaternion(0, 0, 0)
|
| 126 |
+
pose = airsim.Pose(position, heading)
|
| 127 |
+
self.client.simSetVehiclePose(pose, True)
|
| 128 |
+
|
| 129 |
+
def ClearTeleport(self):
|
| 130 |
+
position = airsim.Vector3r(-152.28332052 , 236.00371837, 66.40114393)
|
| 131 |
+
heading = airsim.utils.to_quaternion(0, 0, 0)
|
| 132 |
+
pose = airsim.Pose(position, heading)
|
| 133 |
+
self.client.simSetVehiclePose(pose, True)
|
| 134 |
+
|
| 135 |
+
def FogTeleport(self):
|
| 136 |
+
position = airsim.Vector3r(-430.39138328 , -91.7420777, 13.86624983)
|
| 137 |
+
heading = airsim.utils.to_quaternion(0, 0, 0)
|
| 138 |
+
pose = airsim.Pose(position, heading)
|
| 139 |
+
self.client.simSetVehiclePose(pose, True)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def CloseAPI(self):
|
| 143 |
+
self.client.enableApiControl(False)
|
| 144 |
+
|
| 145 |
+
def KeyboardControl(self,x):
|
| 146 |
+
w = keyboard.KeyboardEvent('down', 150, 'w') # forward
|
| 147 |
+
s = keyboard.KeyboardEvent('down', 150, 's') # back
|
| 148 |
+
a = keyboard.KeyboardEvent('down', 150, 'a') # left
|
| 149 |
+
d = keyboard.KeyboardEvent('down', 150, 'd') # right
|
| 150 |
+
up = keyboard.KeyboardEvent('down', 150, 'up') # up
|
| 151 |
+
down = keyboard.KeyboardEvent('down', 150, 'down') # down
|
| 152 |
+
left = keyboard.KeyboardEvent('down', 150, 'left') # left
|
| 153 |
+
right = keyboard.KeyboardEvent('down', 150, 'right') # right
|
| 154 |
+
k = keyboard.KeyboardEvent('down', 28, 'k') # get control
|
| 155 |
+
l = keyboard.KeyboardEvent('down', 28, 'l') # release control
|
| 156 |
+
if x.event_type == 'down' and x.name == w.name:
|
| 157 |
+
self.client.moveByVelocityBodyFrameAsync(3, 0, 0, 0.5)
|
| 158 |
+
print("forward")
|
| 159 |
+
elif x.event_type == 'down' and x.name == s.name:
|
| 160 |
+
self.client.moveByVelocityBodyFrameAsync(-3, 0, 0, 0.5)
|
| 161 |
+
print("back")
|
| 162 |
+
elif x.event_type == 'down' and x.name == a.name:
|
| 163 |
+
self.client.moveByVelocityBodyFrameAsync(0, -2, 0, 0.5)
|
| 164 |
+
print("left")
|
| 165 |
+
elif x.event_type == 'down' and x.name == d.name:
|
| 166 |
+
self.client.moveByVelocityBodyFrameAsync(0, 2, 0, 0.5)
|
| 167 |
+
print("right")
|
| 168 |
+
elif x.event_type == 'down' and x.name == up.name:
|
| 169 |
+
self.client.moveByVelocityBodyFrameAsync(0, 0, -0.5, 0.5)
|
| 170 |
+
print("up")
|
| 171 |
+
elif x.event_type == 'down' and x.name == down.name:
|
| 172 |
+
self.client.moveByVelocityBodyFrameAsync(0, 0, 0.5, 0.5)
|
| 173 |
+
print("down")
|
| 174 |
+
elif x.event_type == 'down' and x.name == left.name:
|
| 175 |
+
self.client.rotateByYawRateAsync(-20, 0.5)
|
| 176 |
+
print("turn left")
|
| 177 |
+
elif x.event_type == 'down' and x.name == right.name:
|
| 178 |
+
self.client.rotateByYawRateAsync(20, 0.5)
|
| 179 |
+
print("turn right")
|
| 180 |
+
elif x.event_type == 'down' and x.name == k.name:
|
| 181 |
+
# get control
|
| 182 |
+
self.client.enableApiControl(True)
|
| 183 |
+
print("get control")
|
| 184 |
+
# unlock
|
| 185 |
+
self.client.armDisarm(True)
|
| 186 |
+
print("unlock")
|
| 187 |
+
# Async methods returns Future. Call join() to wait for task to complete.
|
| 188 |
+
self.client.takeoffAsync().join()
|
| 189 |
+
print("takeoff")
|
| 190 |
+
elif x.event_type == 'down' and x.name == l.name:
|
| 191 |
+
keyboard.wait("l")
|
| 192 |
+
keyboard.unhook_all()
|
| 193 |
+
else:
|
| 194 |
+
self.client.moveByVelocityBodyFrameAsync(0, 0, 0, 0.5).join()
|
| 195 |
+
self.client.hoverAsync().join()
|
| 196 |
+
print("hovering")
|
| 197 |
+
|
| 198 |
+
def ReplayRoute(self):
|
| 199 |
+
with open('Route.json', 'r', encoding='utf-8') as file:
|
| 200 |
+
data = json.load(file)
|
| 201 |
+
|
| 202 |
+
location_x = data.get("Location.X", [])
|
| 203 |
+
location_y = data.get("Location.Y", [])
|
| 204 |
+
location_z = data.get("Location.Z", [])
|
| 205 |
+
|
| 206 |
+
# lerp
|
| 207 |
+
all_frames = set()
|
| 208 |
+
for arr in [location_x, location_y, location_z]:
|
| 209 |
+
all_frames.update([item["frame"] for item in arr])
|
| 210 |
+
all_frames = sorted(all_frames)
|
| 211 |
+
|
| 212 |
+
interp_x = interpolate_axis(location_x, all_frames)
|
| 213 |
+
interp_y = interpolate_axis(location_y, all_frames)
|
| 214 |
+
interp_z = interpolate_axis(location_z, all_frames)
|
| 215 |
+
|
| 216 |
+
for i in range(len(all_frames) - 1):
|
| 217 |
+
position = airsim.Vector3r(interp_x[i]/100, interp_y[i]/100, -interp_z[i]/100)
|
| 218 |
+
position_next = airsim.Vector3r(interp_x[i+1]/100, interp_y[i+1]/100, -interp_z[i+1]/100)
|
| 219 |
+
time = (all_frames[i+1] - all_frames[i]) / 30 # 30 FPS
|
| 220 |
+
speed = Calc_distance(position, position_next) / time if time > 0 else 1
|
| 221 |
+
print(position)
|
| 222 |
+
self.client.moveToPositionAsync(position.x_val, position.y_val, position.z_val, speed).join()
|
| 223 |
+
|
| 224 |
+
|
| 225 |
+
def interpolate_axis(axis_list, target_frames):
|
| 226 |
+
if not axis_list:
|
| 227 |
+
return [0.0] * len(target_frames)
|
| 228 |
+
axis_list = sorted(axis_list, key=lambda x: x["frame"])
|
| 229 |
+
frames = [item["frame"] for item in axis_list]
|
| 230 |
+
values = [item["value"] for item in axis_list]
|
| 231 |
+
result = []
|
| 232 |
+
for f in target_frames:
|
| 233 |
+
if f <= frames[0]:
|
| 234 |
+
result.append(values[0])
|
| 235 |
+
elif f >= frames[-1]:
|
| 236 |
+
result.append(values[-1])
|
| 237 |
+
else:
|
| 238 |
+
for i in range(len(frames) - 1):
|
| 239 |
+
if frames[i] <= f <= frames[i+1]:
|
| 240 |
+
t = (f - frames[i]) / (frames[i+1] - frames[i])
|
| 241 |
+
v = values[i] + t * (values[i+1] - values[i])
|
| 242 |
+
result.append(v)
|
| 243 |
+
break
|
| 244 |
+
return result
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
def Calc_distance(pos1, pos2):
|
| 248 |
+
return math.sqrt((pos1.x_val - pos2.x_val) ** 2 + (pos1.y_val - pos2.y_val) ** 2 + (pos1.z_val - pos2.z_val) ** 2)
|
| 249 |
+
|
| 250 |
+
if __name__ == '__main__':
|
| 251 |
+
dronne = Drone()
|
| 252 |
+
# add sleep to make sure excute at same time
|
| 253 |
+
dronne.ReplayRoute()
|
| 254 |
+
|
| 255 |
+
dronecontrol = Drone()
|
| 256 |
+
|
FileConverter.py
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from PIL import Image
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
def batch_convert_images(input_dir, output_dir):
|
| 5 |
+
# Create the output directory if it doesn't exist
|
| 6 |
+
if not os.path.exists(output_dir):
|
| 7 |
+
os.makedirs(output_dir)
|
| 8 |
+
|
| 9 |
+
# Process each file in the input directory
|
| 10 |
+
for filename in os.listdir(input_dir):
|
| 11 |
+
# Check for .ppm or .pgm files (case-insensitive)
|
| 12 |
+
if filename.lower().endswith('.ppm') or filename.lower().endswith('.pgm'):
|
| 13 |
+
img_path = os.path.join(input_dir, filename)
|
| 14 |
+
img = Image.open(img_path)
|
| 15 |
+
|
| 16 |
+
# Create the new filename with .png extension
|
| 17 |
+
new_filename = os.path.splitext(filename)[0] + '.png'
|
| 18 |
+
save_path = os.path.join(output_dir, new_filename)
|
| 19 |
+
|
| 20 |
+
# Save the image as .png
|
| 21 |
+
img.save(save_path)
|
| 22 |
+
print(f"Converted: {filename} -> {new_filename}")
|
| 23 |
+
|
| 24 |
+
# Example usage
|
| 25 |
+
input_dir = 'E:/THU_Projects/DataSets/2025-03-22-20-29-50/images'
|
| 26 |
+
output_dir = 'E:/THU_Projects/DataSets/2025-03-22-20-29-50/PNG'
|
| 27 |
+
batch_convert_images(input_dir, output_dir)
|
LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright [yyyy] [name of copyright owner]
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
SeqOutputer.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Import built-in modules
|
| 2 |
+
from collections import defaultdict
|
| 3 |
+
import json
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
# Import local modules
|
| 7 |
+
import unreal
|
| 8 |
+
|
| 9 |
+
DIR = os.path.dirname(os.path.abspath(__file__))
|
| 10 |
+
|
| 11 |
+
def unreal_progress(tasks, label="����", total=None):
|
| 12 |
+
total = total if total else len(tasks)
|
| 13 |
+
with unreal.ScopedSlowTask(total, label) as task:
|
| 14 |
+
task.make_dialog(True)
|
| 15 |
+
for i, item in enumerate(tasks):
|
| 16 |
+
if task.should_cancel():
|
| 17 |
+
break
|
| 18 |
+
task.enter_progress_frame(1, "%s %s/%s" % (label, i, total))
|
| 19 |
+
yield item
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def main():
|
| 23 |
+
# NOTE: ��ȡ sequence
|
| 24 |
+
sequence = unreal.load_asset('/Game/RecSeq')
|
| 25 |
+
# NOTE: �ռ� sequence �������е� binding
|
| 26 |
+
binding_dict = defaultdict(list)
|
| 27 |
+
for binding in sequence.get_bindings():
|
| 28 |
+
binding_dict[binding.get_name()].append(binding)
|
| 29 |
+
|
| 30 |
+
# NOTE: ��������Ϊ Face �� binding
|
| 31 |
+
for binding in unreal_progress(binding_dict.get("BP_FlyingPawn", []), "Transform"):
|
| 32 |
+
# NOTE: ��ȡ�ؼ�֡ channel ����
|
| 33 |
+
keys_dict = {}
|
| 34 |
+
for track in binding.get_tracks():
|
| 35 |
+
for section in track.get_sections():
|
| 36 |
+
for channel in unreal_progress(section.get_channels(), "KeyFrame"):
|
| 37 |
+
if not channel.get_num_keys():
|
| 38 |
+
continue
|
| 39 |
+
keys = []
|
| 40 |
+
for key in channel.get_keys():
|
| 41 |
+
frame_time = key.get_time()
|
| 42 |
+
frame = frame_time.frame_number.value + frame_time.sub_frame
|
| 43 |
+
keys.append({"frame": frame, "value": key.get_value()})
|
| 44 |
+
|
| 45 |
+
keys_dict[channel.get_name()] = keys
|
| 46 |
+
|
| 47 |
+
# NOTE: ���� json
|
| 48 |
+
name = binding.get_parent().get_name()
|
| 49 |
+
export_path = os.path.join(DIR, "{0}.json".format(name))
|
| 50 |
+
with open(export_path, "w") as wf:
|
| 51 |
+
json.dump(keys_dict, wf, indent=4)
|
| 52 |
+
|
| 53 |
+
main()
|
airsim/__init__.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from .client import *
|
| 2 |
+
from .utils import *
|
| 3 |
+
from .types import *
|
| 4 |
+
|
| 5 |
+
__version__ = "1.7.0"
|
airsim/__pycache__/__init__.cpython-39.pyc
ADDED
|
Binary file (232 Bytes). View file
|
|
|
airsim/__pycache__/client.cpython-39.pyc
ADDED
|
Binary file (78.2 kB). View file
|
|
|
airsim/__pycache__/types.cpython-39.pyc
ADDED
|
Binary file (24.1 kB). View file
|
|
|
airsim/__pycache__/utils.cpython-39.pyc
ADDED
|
Binary file (5.21 kB). View file
|
|
|
airsim/client.py
ADDED
|
@@ -0,0 +1,1621 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import print_function
|
| 2 |
+
|
| 3 |
+
from .utils import *
|
| 4 |
+
from .types import *
|
| 5 |
+
|
| 6 |
+
import msgpackrpc #install as admin: pip install msgpack-rpc-python
|
| 7 |
+
import numpy as np #pip install numpy
|
| 8 |
+
import msgpack
|
| 9 |
+
import time
|
| 10 |
+
import math
|
| 11 |
+
import logging
|
| 12 |
+
|
| 13 |
+
class VehicleClient:
|
| 14 |
+
def __init__(self, ip = "", port = 41451, timeout_value = 3600):
|
| 15 |
+
if (ip == ""):
|
| 16 |
+
ip = "127.0.0.1"
|
| 17 |
+
self.client = msgpackrpc.Client(msgpackrpc.Address(ip, port), timeout = timeout_value, pack_encoding = 'utf-8', unpack_encoding = 'utf-8')
|
| 18 |
+
|
| 19 |
+
#----------------------------------- Common vehicle APIs ---------------------------------------------
|
| 20 |
+
def reset(self):
|
| 21 |
+
"""
|
| 22 |
+
Reset the vehicle to its original starting state
|
| 23 |
+
|
| 24 |
+
Note that you must call `enableApiControl` and `armDisarm` again after the call to reset
|
| 25 |
+
"""
|
| 26 |
+
self.client.call('reset')
|
| 27 |
+
|
| 28 |
+
def ping(self):
|
| 29 |
+
"""
|
| 30 |
+
If connection is established then this call will return true otherwise it will be blocked until timeout
|
| 31 |
+
|
| 32 |
+
Returns:
|
| 33 |
+
bool:
|
| 34 |
+
"""
|
| 35 |
+
return self.client.call('ping')
|
| 36 |
+
|
| 37 |
+
def getClientVersion(self):
|
| 38 |
+
return 1 # sync with C++ client
|
| 39 |
+
|
| 40 |
+
def getServerVersion(self):
|
| 41 |
+
return self.client.call('getServerVersion')
|
| 42 |
+
|
| 43 |
+
def getMinRequiredServerVersion(self):
|
| 44 |
+
return 1 # sync with C++ client
|
| 45 |
+
|
| 46 |
+
def getMinRequiredClientVersion(self):
|
| 47 |
+
return self.client.call('getMinRequiredClientVersion')
|
| 48 |
+
|
| 49 |
+
#basic flight control
|
| 50 |
+
def enableApiControl(self, is_enabled, vehicle_name = ''):
|
| 51 |
+
"""
|
| 52 |
+
Enables or disables API control for vehicle corresponding to vehicle_name
|
| 53 |
+
|
| 54 |
+
Args:
|
| 55 |
+
is_enabled (bool): True to enable, False to disable API control
|
| 56 |
+
vehicle_name (str, optional): Name of the vehicle to send this command to
|
| 57 |
+
"""
|
| 58 |
+
self.client.call('enableApiControl', is_enabled, vehicle_name)
|
| 59 |
+
|
| 60 |
+
def isApiControlEnabled(self, vehicle_name = ''):
|
| 61 |
+
"""
|
| 62 |
+
Returns true if API control is established.
|
| 63 |
+
|
| 64 |
+
If false (which is default) then API calls would be ignored. After a successful call to `enableApiControl`, `isApiControlEnabled` should return true.
|
| 65 |
+
|
| 66 |
+
Args:
|
| 67 |
+
vehicle_name (str, optional): Name of the vehicle
|
| 68 |
+
|
| 69 |
+
Returns:
|
| 70 |
+
bool: If API control is enabled
|
| 71 |
+
"""
|
| 72 |
+
return self.client.call('isApiControlEnabled', vehicle_name)
|
| 73 |
+
|
| 74 |
+
def armDisarm(self, arm, vehicle_name = ''):
|
| 75 |
+
"""
|
| 76 |
+
Arms or disarms vehicle
|
| 77 |
+
|
| 78 |
+
Args:
|
| 79 |
+
arm (bool): True to arm, False to disarm the vehicle
|
| 80 |
+
vehicle_name (str, optional): Name of the vehicle to send this command to
|
| 81 |
+
|
| 82 |
+
Returns:
|
| 83 |
+
bool: Success
|
| 84 |
+
"""
|
| 85 |
+
return self.client.call('armDisarm', arm, vehicle_name)
|
| 86 |
+
|
| 87 |
+
def simPause(self, is_paused):
|
| 88 |
+
"""
|
| 89 |
+
Pauses simulation
|
| 90 |
+
|
| 91 |
+
Args:
|
| 92 |
+
is_paused (bool): True to pause the simulation, False to release
|
| 93 |
+
"""
|
| 94 |
+
self.client.call('simPause', is_paused)
|
| 95 |
+
|
| 96 |
+
def simIsPause(self):
|
| 97 |
+
"""
|
| 98 |
+
Returns true if the simulation is paused
|
| 99 |
+
|
| 100 |
+
Returns:
|
| 101 |
+
bool: If the simulation is paused
|
| 102 |
+
"""
|
| 103 |
+
return self.client.call("simIsPaused")
|
| 104 |
+
|
| 105 |
+
def simContinueForTime(self, seconds):
|
| 106 |
+
"""
|
| 107 |
+
Continue the simulation for the specified number of seconds
|
| 108 |
+
|
| 109 |
+
Args:
|
| 110 |
+
seconds (float): Time to run the simulation for
|
| 111 |
+
"""
|
| 112 |
+
self.client.call('simContinueForTime', seconds)
|
| 113 |
+
|
| 114 |
+
def simContinueForFrames(self, frames):
|
| 115 |
+
"""
|
| 116 |
+
Continue (or resume if paused) the simulation for the specified number of frames, after which the simulation will be paused.
|
| 117 |
+
|
| 118 |
+
Args:
|
| 119 |
+
frames (int): Frames to run the simulation for
|
| 120 |
+
"""
|
| 121 |
+
self.client.call('simContinueForFrames', frames)
|
| 122 |
+
|
| 123 |
+
def getHomeGeoPoint(self, vehicle_name = ''):
|
| 124 |
+
"""
|
| 125 |
+
Get the Home location of the vehicle
|
| 126 |
+
|
| 127 |
+
Args:
|
| 128 |
+
vehicle_name (str, optional): Name of vehicle to get home location of
|
| 129 |
+
|
| 130 |
+
Returns:
|
| 131 |
+
GeoPoint: Home location of the vehicle
|
| 132 |
+
"""
|
| 133 |
+
return GeoPoint.from_msgpack(self.client.call('getHomeGeoPoint', vehicle_name))
|
| 134 |
+
|
| 135 |
+
def confirmConnection(self):
|
| 136 |
+
"""
|
| 137 |
+
Checks state of connection every 1 sec and reports it in Console so user can see the progress for connection.
|
| 138 |
+
"""
|
| 139 |
+
if self.ping():
|
| 140 |
+
print("Connected!")
|
| 141 |
+
else:
|
| 142 |
+
print("Ping returned false!")
|
| 143 |
+
server_ver = self.getServerVersion()
|
| 144 |
+
client_ver = self.getClientVersion()
|
| 145 |
+
server_min_ver = self.getMinRequiredServerVersion()
|
| 146 |
+
client_min_ver = self.getMinRequiredClientVersion()
|
| 147 |
+
|
| 148 |
+
ver_info = "Client Ver:" + str(client_ver) + " (Min Req: " + str(client_min_ver) + \
|
| 149 |
+
"), Server Ver:" + str(server_ver) + " (Min Req: " + str(server_min_ver) + ")"
|
| 150 |
+
|
| 151 |
+
if server_ver < server_min_ver:
|
| 152 |
+
print(ver_info, file=sys.stderr)
|
| 153 |
+
print("AirSim server is of older version and not supported by this client. Please upgrade!")
|
| 154 |
+
elif client_ver < client_min_ver:
|
| 155 |
+
print(ver_info, file=sys.stderr)
|
| 156 |
+
print("AirSim client is of older version and not supported by this server. Please upgrade!")
|
| 157 |
+
else:
|
| 158 |
+
print(ver_info)
|
| 159 |
+
print('')
|
| 160 |
+
|
| 161 |
+
def simSetLightIntensity(self, light_name, intensity):
|
| 162 |
+
"""
|
| 163 |
+
Change intensity of named light
|
| 164 |
+
|
| 165 |
+
Args:
|
| 166 |
+
light_name (str): Name of light to change
|
| 167 |
+
intensity (float): New intensity value
|
| 168 |
+
|
| 169 |
+
Returns:
|
| 170 |
+
bool: True if successful, otherwise False
|
| 171 |
+
"""
|
| 172 |
+
return self.client.call("simSetLightIntensity", light_name, intensity)
|
| 173 |
+
|
| 174 |
+
def simSwapTextures(self, tags, tex_id = 0, component_id = 0, material_id = 0):
|
| 175 |
+
"""
|
| 176 |
+
Runtime Swap Texture API
|
| 177 |
+
|
| 178 |
+
See https://microsoft.github.io/AirSim/retexturing/ for details
|
| 179 |
+
|
| 180 |
+
Args:
|
| 181 |
+
tags (str): string of "," or ", " delimited tags to identify on which actors to perform the swap
|
| 182 |
+
tex_id (int, optional): indexes the array of textures assigned to each actor undergoing a swap
|
| 183 |
+
|
| 184 |
+
If out-of-bounds for some object's texture set, it will be taken modulo the number of textures that were available
|
| 185 |
+
component_id (int, optional):
|
| 186 |
+
material_id (int, optional):
|
| 187 |
+
|
| 188 |
+
Returns:
|
| 189 |
+
list[str]: List of objects which matched the provided tags and had the texture swap perfomed
|
| 190 |
+
"""
|
| 191 |
+
return self.client.call("simSwapTextures", tags, tex_id, component_id, material_id)
|
| 192 |
+
|
| 193 |
+
def simSetObjectMaterial(self, object_name, material_name, component_id = 0):
|
| 194 |
+
"""
|
| 195 |
+
Runtime Swap Texture API
|
| 196 |
+
See https://microsoft.github.io/AirSim/retexturing/ for details
|
| 197 |
+
Args:
|
| 198 |
+
object_name (str): name of object to set material for
|
| 199 |
+
material_name (str): name of material to set for object
|
| 200 |
+
component_id (int, optional) : index of material elements
|
| 201 |
+
|
| 202 |
+
Returns:
|
| 203 |
+
bool: True if material was set
|
| 204 |
+
"""
|
| 205 |
+
return self.client.call("simSetObjectMaterial", object_name, material_name, component_id)
|
| 206 |
+
|
| 207 |
+
def simSetObjectMaterialFromTexture(self, object_name, texture_path, component_id = 0):
|
| 208 |
+
"""
|
| 209 |
+
Runtime Swap Texture API
|
| 210 |
+
See https://microsoft.github.io/AirSim/retexturing/ for details
|
| 211 |
+
Args:
|
| 212 |
+
object_name (str): name of object to set material for
|
| 213 |
+
texture_path (str): path to texture to set for object
|
| 214 |
+
component_id (int, optional) : index of material elements
|
| 215 |
+
|
| 216 |
+
Returns:
|
| 217 |
+
bool: True if material was set
|
| 218 |
+
"""
|
| 219 |
+
return self.client.call("simSetObjectMaterialFromTexture", object_name, texture_path, component_id)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
# time-of-day control
|
| 223 |
+
#time - of - day control
|
| 224 |
+
def simSetTimeOfDay(self, is_enabled, start_datetime = "", is_start_datetime_dst = False, celestial_clock_speed = 1, update_interval_secs = 60, move_sun = True):
|
| 225 |
+
"""
|
| 226 |
+
Control the position of Sun in the environment
|
| 227 |
+
|
| 228 |
+
Sun's position is computed using the coordinates specified in `OriginGeopoint` in settings for the date-time specified in the argument,
|
| 229 |
+
else if the string is empty, current date & time is used
|
| 230 |
+
|
| 231 |
+
Args:
|
| 232 |
+
is_enabled (bool): True to enable time-of-day effect, False to reset the position to original
|
| 233 |
+
start_datetime (str, optional): Date & Time in %Y-%m-%d %H:%M:%S format, e.g. `2018-02-12 15:20:00`
|
| 234 |
+
is_start_datetime_dst (bool, optional): True to adjust for Daylight Savings Time
|
| 235 |
+
celestial_clock_speed (float, optional): Run celestial clock faster or slower than simulation clock
|
| 236 |
+
E.g. Value 100 means for every 1 second of simulation clock, Sun's position is advanced by 100 seconds
|
| 237 |
+
so Sun will move in sky much faster
|
| 238 |
+
update_interval_secs (float, optional): Interval to update the Sun's position
|
| 239 |
+
move_sun (bool, optional): Whether or not to move the Sun
|
| 240 |
+
"""
|
| 241 |
+
self.client.call('simSetTimeOfDay', is_enabled, start_datetime, is_start_datetime_dst, celestial_clock_speed, update_interval_secs, move_sun)
|
| 242 |
+
|
| 243 |
+
#weather
|
| 244 |
+
def simEnableWeather(self, enable):
|
| 245 |
+
"""
|
| 246 |
+
Enable Weather effects. Needs to be called before using `simSetWeatherParameter` API
|
| 247 |
+
|
| 248 |
+
Args:
|
| 249 |
+
enable (bool): True to enable, False to disable
|
| 250 |
+
"""
|
| 251 |
+
self.client.call('simEnableWeather', enable)
|
| 252 |
+
|
| 253 |
+
def simSetWeatherParameter(self, param, val):
|
| 254 |
+
"""
|
| 255 |
+
Enable various weather effects
|
| 256 |
+
|
| 257 |
+
Args:
|
| 258 |
+
param (WeatherParameter): Weather effect to be enabled
|
| 259 |
+
val (float): Intensity of the effect, Range 0-1
|
| 260 |
+
"""
|
| 261 |
+
self.client.call('simSetWeatherParameter', param, val)
|
| 262 |
+
|
| 263 |
+
#camera control
|
| 264 |
+
#simGetImage returns compressed png in array of bytes
|
| 265 |
+
#image_type uses one of the ImageType members
|
| 266 |
+
def simGetImage(self, camera_name, image_type, vehicle_name = '', external = False):
|
| 267 |
+
"""
|
| 268 |
+
Get a single image
|
| 269 |
+
|
| 270 |
+
Returns bytes of png format image which can be dumped into abinary file to create .png image
|
| 271 |
+
`string_to_uint8_array()` can be used to convert into Numpy unit8 array
|
| 272 |
+
See https://microsoft.github.io/AirSim/image_apis/ for details
|
| 273 |
+
|
| 274 |
+
Args:
|
| 275 |
+
camera_name (str): Name of the camera, for backwards compatibility, ID numbers such as 0,1,etc. can also be used
|
| 276 |
+
image_type (ImageType): Type of image required
|
| 277 |
+
vehicle_name (str, optional): Name of the vehicle with the camera
|
| 278 |
+
external (bool, optional): Whether the camera is an External Camera
|
| 279 |
+
|
| 280 |
+
Returns:
|
| 281 |
+
Binary string literal of compressed png image
|
| 282 |
+
"""
|
| 283 |
+
#todo : in future remove below, it's only for compatibility to pre v1.2
|
| 284 |
+
camera_name = str(camera_name)
|
| 285 |
+
|
| 286 |
+
#because this method returns std::vector < uint8>, msgpack decides to encode it as a string unfortunately.
|
| 287 |
+
result = self.client.call('simGetImage', camera_name, image_type, vehicle_name, external)
|
| 288 |
+
if (result == "" or result == "\0"):
|
| 289 |
+
return None
|
| 290 |
+
return result
|
| 291 |
+
|
| 292 |
+
#camera control
|
| 293 |
+
#simGetImage returns compressed png in array of bytes
|
| 294 |
+
#image_type uses one of the ImageType members
|
| 295 |
+
def simGetImages(self, requests, vehicle_name = '', external = False):
|
| 296 |
+
"""
|
| 297 |
+
Get multiple images
|
| 298 |
+
|
| 299 |
+
See https://microsoft.github.io/AirSim/image_apis/ for details and examples
|
| 300 |
+
|
| 301 |
+
Args:
|
| 302 |
+
requests (list[ImageRequest]): Images required
|
| 303 |
+
vehicle_name (str, optional): Name of vehicle associated with the camera
|
| 304 |
+
external (bool, optional): Whether the camera is an External Camera
|
| 305 |
+
|
| 306 |
+
Returns:
|
| 307 |
+
list[ImageResponse]:
|
| 308 |
+
"""
|
| 309 |
+
responses_raw = self.client.call('simGetImages', requests, vehicle_name, external)
|
| 310 |
+
return [ImageResponse.from_msgpack(response_raw) for response_raw in responses_raw]
|
| 311 |
+
|
| 312 |
+
|
| 313 |
+
|
| 314 |
+
#CinemAirSim
|
| 315 |
+
def simGetPresetLensSettings(self, camera_name, vehicle_name = '', external = False):
|
| 316 |
+
result = self.client.call('simGetPresetLensSettings', camera_name, vehicle_name, external)
|
| 317 |
+
if (result == "" or result == "\0"):
|
| 318 |
+
return None
|
| 319 |
+
return result
|
| 320 |
+
|
| 321 |
+
def simGetLensSettings(self, camera_name, vehicle_name = '', external = False):
|
| 322 |
+
result = self.client.call('simGetLensSettings', camera_name, vehicle_name, external)
|
| 323 |
+
if (result == "" or result == "\0"):
|
| 324 |
+
return None
|
| 325 |
+
return result
|
| 326 |
+
|
| 327 |
+
def simSetPresetLensSettings(self, preset_lens_settings, camera_name, vehicle_name = '', external = False):
|
| 328 |
+
self.client.call("simSetPresetLensSettings", preset_lens_settings, camera_name, vehicle_name, external)
|
| 329 |
+
|
| 330 |
+
def simGetPresetFilmbackSettings(self, camera_name, vehicle_name = '', external = False):
|
| 331 |
+
result = self.client.call('simGetPresetFilmbackSettings', camera_name, vehicle_name, external)
|
| 332 |
+
if (result == "" or result == "\0"):
|
| 333 |
+
return None
|
| 334 |
+
return result
|
| 335 |
+
|
| 336 |
+
def simSetPresetFilmbackSettings(self, preset_filmback_settings, camera_name, vehicle_name = '', external = False):
|
| 337 |
+
self.client.call("simSetPresetFilmbackSettings", preset_filmback_settings, camera_name, vehicle_name, external)
|
| 338 |
+
|
| 339 |
+
def simGetFilmbackSettings(self, camera_name, vehicle_name = '', external = False):
|
| 340 |
+
result = self.client.call('simGetFilmbackSettings', camera_name, vehicle_name, external)
|
| 341 |
+
if (result == "" or result == "\0"):
|
| 342 |
+
return None
|
| 343 |
+
return result
|
| 344 |
+
|
| 345 |
+
def simSetFilmbackSettings(self, sensor_width, sensor_height, camera_name, vehicle_name = '', external = False):
|
| 346 |
+
return self.client.call("simSetFilmbackSettings", sensor_width, sensor_height, camera_name, vehicle_name, external)
|
| 347 |
+
|
| 348 |
+
def simGetFocalLength(self, camera_name, vehicle_name = '', external = False):
|
| 349 |
+
return self.client.call("simGetFocalLength", camera_name, vehicle_name, external)
|
| 350 |
+
|
| 351 |
+
def simSetFocalLength(self, focal_length, camera_name, vehicle_name = '', external = False):
|
| 352 |
+
self.client.call("simSetFocalLength", focal_length, camera_name, vehicle_name, external)
|
| 353 |
+
|
| 354 |
+
def simEnableManualFocus(self, enable, camera_name, vehicle_name = '', external = False):
|
| 355 |
+
self.client.call("simEnableManualFocus", enable, camera_name, vehicle_name, external)
|
| 356 |
+
|
| 357 |
+
def simGetFocusDistance(self, camera_name, vehicle_name = '', external = False):
|
| 358 |
+
return self.client.call("simGetFocusDistance", camera_name, vehicle_name, external)
|
| 359 |
+
|
| 360 |
+
def simSetFocusDistance(self, focus_distance, camera_name, vehicle_name = '', external = False):
|
| 361 |
+
self.client.call("simSetFocusDistance", focus_distance, camera_name, vehicle_name, external)
|
| 362 |
+
|
| 363 |
+
def simGetFocusAperture(self, camera_name, vehicle_name = '', external = False):
|
| 364 |
+
return self.client.call("simGetFocusAperture", camera_name, vehicle_name, external)
|
| 365 |
+
|
| 366 |
+
def simSetFocusAperture(self, focus_aperture, camera_name, vehicle_name = '', external = False):
|
| 367 |
+
self.client.call("simSetFocusAperture", focus_aperture, camera_name, vehicle_name, external)
|
| 368 |
+
|
| 369 |
+
def simEnableFocusPlane(self, enable, camera_name, vehicle_name = '', external = False):
|
| 370 |
+
self.client.call("simEnableFocusPlane", enable, camera_name, vehicle_name, external)
|
| 371 |
+
|
| 372 |
+
def simGetCurrentFieldOfView(self, camera_name, vehicle_name = '', external = False):
|
| 373 |
+
return self.client.call("simGetCurrentFieldOfView", camera_name, vehicle_name, external)
|
| 374 |
+
|
| 375 |
+
#End CinemAirSim
|
| 376 |
+
def simTestLineOfSightToPoint(self, point, vehicle_name = ''):
|
| 377 |
+
"""
|
| 378 |
+
Returns whether the target point is visible from the perspective of the inputted vehicle
|
| 379 |
+
|
| 380 |
+
Args:
|
| 381 |
+
point (GeoPoint): target point
|
| 382 |
+
vehicle_name (str, optional): Name of vehicle
|
| 383 |
+
|
| 384 |
+
Returns:
|
| 385 |
+
[bool]: Success
|
| 386 |
+
"""
|
| 387 |
+
return self.client.call('simTestLineOfSightToPoint', point, vehicle_name)
|
| 388 |
+
|
| 389 |
+
def simTestLineOfSightBetweenPoints(self, point1, point2):
|
| 390 |
+
"""
|
| 391 |
+
Returns whether the target point is visible from the perspective of the source point
|
| 392 |
+
|
| 393 |
+
Args:
|
| 394 |
+
point1 (GeoPoint): source point
|
| 395 |
+
point2 (GeoPoint): target point
|
| 396 |
+
|
| 397 |
+
Returns:
|
| 398 |
+
[bool]: Success
|
| 399 |
+
"""
|
| 400 |
+
return self.client.call('simTestLineOfSightBetweenPoints', point1, point2)
|
| 401 |
+
|
| 402 |
+
def simGetWorldExtents(self):
|
| 403 |
+
"""
|
| 404 |
+
Returns a list of GeoPoints representing the minimum and maximum extents of the world
|
| 405 |
+
|
| 406 |
+
Returns:
|
| 407 |
+
list[GeoPoint]
|
| 408 |
+
"""
|
| 409 |
+
responses_raw = self.client.call('simGetWorldExtents')
|
| 410 |
+
return [GeoPoint.from_msgpack(response_raw) for response_raw in responses_raw]
|
| 411 |
+
|
| 412 |
+
def simRunConsoleCommand(self, command):
|
| 413 |
+
"""
|
| 414 |
+
Allows the client to execute a command in Unreal's native console, via an API.
|
| 415 |
+
Affords access to the countless built-in commands such as "stat unit", "stat fps", "open [map]", adjust any config settings, etc. etc.
|
| 416 |
+
Allows the user to create bespoke APIs very easily, by adding a custom event to the level blueprint, and then calling the console command "ce MyEventName [args]". No recompilation of AirSim needed!
|
| 417 |
+
|
| 418 |
+
Args:
|
| 419 |
+
command ([string]): Desired Unreal Engine Console command to run
|
| 420 |
+
|
| 421 |
+
Returns:
|
| 422 |
+
[bool]: Success
|
| 423 |
+
"""
|
| 424 |
+
return self.client.call('simRunConsoleCommand', command)
|
| 425 |
+
|
| 426 |
+
#gets the static meshes in the unreal scene
|
| 427 |
+
def simGetMeshPositionVertexBuffers(self):
|
| 428 |
+
"""
|
| 429 |
+
Returns the static meshes that make up the scene
|
| 430 |
+
|
| 431 |
+
See https://microsoft.github.io/AirSim/meshes/ for details and how to use this
|
| 432 |
+
|
| 433 |
+
Returns:
|
| 434 |
+
list[MeshPositionVertexBuffersResponse]:
|
| 435 |
+
"""
|
| 436 |
+
responses_raw = self.client.call('simGetMeshPositionVertexBuffers')
|
| 437 |
+
return [MeshPositionVertexBuffersResponse.from_msgpack(response_raw) for response_raw in responses_raw]
|
| 438 |
+
|
| 439 |
+
def simGetCollisionInfo(self, vehicle_name = ''):
|
| 440 |
+
"""
|
| 441 |
+
Args:
|
| 442 |
+
vehicle_name (str, optional): Name of the Vehicle to get the info of
|
| 443 |
+
|
| 444 |
+
Returns:
|
| 445 |
+
CollisionInfo:
|
| 446 |
+
"""
|
| 447 |
+
return CollisionInfo.from_msgpack(self.client.call('simGetCollisionInfo', vehicle_name))
|
| 448 |
+
|
| 449 |
+
def simSetVehiclePose(self, pose, ignore_collision, vehicle_name = ''):
|
| 450 |
+
"""
|
| 451 |
+
Set the pose of the vehicle
|
| 452 |
+
|
| 453 |
+
If you don't want to change position (or orientation) then just set components of position (or orientation) to floating point nan values
|
| 454 |
+
|
| 455 |
+
Args:
|
| 456 |
+
pose (Pose): Desired Pose pf the vehicle
|
| 457 |
+
ignore_collision (bool): Whether to ignore any collision or not
|
| 458 |
+
vehicle_name (str, optional): Name of the vehicle to move
|
| 459 |
+
"""
|
| 460 |
+
self.client.call('simSetVehiclePose', pose, ignore_collision, vehicle_name)
|
| 461 |
+
|
| 462 |
+
def simGetVehiclePose(self, vehicle_name = ''):
|
| 463 |
+
"""
|
| 464 |
+
The position inside the returned Pose is in the frame of the vehicle's starting point
|
| 465 |
+
|
| 466 |
+
Args:
|
| 467 |
+
vehicle_name (str, optional): Name of the vehicle to get the Pose of
|
| 468 |
+
|
| 469 |
+
Returns:
|
| 470 |
+
Pose:
|
| 471 |
+
"""
|
| 472 |
+
pose = self.client.call('simGetVehiclePose', vehicle_name)
|
| 473 |
+
return Pose.from_msgpack(pose)
|
| 474 |
+
|
| 475 |
+
def simSetTraceLine(self, color_rgba, thickness=1.0, vehicle_name = ''):
|
| 476 |
+
"""
|
| 477 |
+
Modify the color and thickness of the line when Tracing is enabled
|
| 478 |
+
|
| 479 |
+
Tracing can be enabled by pressing T in the Editor or setting `EnableTrace` to `True` in the Vehicle Settings
|
| 480 |
+
|
| 481 |
+
Args:
|
| 482 |
+
color_rgba (list): desired RGBA values from 0.0 to 1.0
|
| 483 |
+
thickness (float, optional): Thickness of the line
|
| 484 |
+
vehicle_name (string, optional): Name of the vehicle to set Trace line values for
|
| 485 |
+
"""
|
| 486 |
+
self.client.call('simSetTraceLine', color_rgba, thickness, vehicle_name)
|
| 487 |
+
|
| 488 |
+
def simGetObjectPose(self, object_name):
|
| 489 |
+
"""
|
| 490 |
+
The position inside the returned Pose is in the world frame
|
| 491 |
+
|
| 492 |
+
Args:
|
| 493 |
+
object_name (str): Object to get the Pose of
|
| 494 |
+
|
| 495 |
+
Returns:
|
| 496 |
+
Pose:
|
| 497 |
+
"""
|
| 498 |
+
pose = self.client.call('simGetObjectPose', object_name)
|
| 499 |
+
return Pose.from_msgpack(pose)
|
| 500 |
+
|
| 501 |
+
def simSetObjectPose(self, object_name, pose, teleport = True):
|
| 502 |
+
"""
|
| 503 |
+
Set the pose of the object(actor) in the environment
|
| 504 |
+
|
| 505 |
+
The specified actor must have Mobility set to movable, otherwise there will be undefined behaviour.
|
| 506 |
+
See https://www.unrealengine.com/en-US/blog/moving-physical-objects for details on how to set Mobility and the effect of Teleport parameter
|
| 507 |
+
|
| 508 |
+
Args:
|
| 509 |
+
object_name (str): Name of the object(actor) to move
|
| 510 |
+
pose (Pose): Desired Pose of the object
|
| 511 |
+
teleport (bool, optional): Whether to move the object immediately without affecting their velocity
|
| 512 |
+
|
| 513 |
+
Returns:
|
| 514 |
+
bool: If the move was successful
|
| 515 |
+
"""
|
| 516 |
+
return self.client.call('simSetObjectPose', object_name, pose, teleport)
|
| 517 |
+
|
| 518 |
+
def simGetObjectScale(self, object_name):
|
| 519 |
+
"""
|
| 520 |
+
Gets scale of an object in the world
|
| 521 |
+
|
| 522 |
+
Args:
|
| 523 |
+
object_name (str): Object to get the scale of
|
| 524 |
+
|
| 525 |
+
Returns:
|
| 526 |
+
airsim.Vector3r: Scale
|
| 527 |
+
"""
|
| 528 |
+
scale = self.client.call('simGetObjectScale', object_name)
|
| 529 |
+
return Vector3r.from_msgpack(scale)
|
| 530 |
+
|
| 531 |
+
def simSetObjectScale(self, object_name, scale_vector):
|
| 532 |
+
"""
|
| 533 |
+
Sets scale of an object in the world
|
| 534 |
+
|
| 535 |
+
Args:
|
| 536 |
+
object_name (str): Object to set the scale of
|
| 537 |
+
scale_vector (airsim.Vector3r): Desired scale of object
|
| 538 |
+
|
| 539 |
+
Returns:
|
| 540 |
+
bool: True if scale change was successful
|
| 541 |
+
"""
|
| 542 |
+
return self.client.call('simSetObjectScale', object_name, scale_vector)
|
| 543 |
+
|
| 544 |
+
def simListSceneObjects(self, name_regex = '.*'):
|
| 545 |
+
"""
|
| 546 |
+
Lists the objects present in the environment
|
| 547 |
+
|
| 548 |
+
Default behaviour is to list all objects, regex can be used to return smaller list of matching objects or actors
|
| 549 |
+
|
| 550 |
+
Args:
|
| 551 |
+
name_regex (str, optional): String to match actor names against, e.g. "Cylinder.*"
|
| 552 |
+
|
| 553 |
+
Returns:
|
| 554 |
+
list[str]: List containing all the names
|
| 555 |
+
"""
|
| 556 |
+
return self.client.call('simListSceneObjects', name_regex)
|
| 557 |
+
|
| 558 |
+
def simLoadLevel(self, level_name):
|
| 559 |
+
"""
|
| 560 |
+
Loads a level specified by its name
|
| 561 |
+
|
| 562 |
+
Args:
|
| 563 |
+
level_name (str): Name of the level to load
|
| 564 |
+
|
| 565 |
+
Returns:
|
| 566 |
+
bool: True if the level was successfully loaded
|
| 567 |
+
"""
|
| 568 |
+
return self.client.call('simLoadLevel', level_name)
|
| 569 |
+
|
| 570 |
+
def simListAssets(self):
|
| 571 |
+
"""
|
| 572 |
+
Lists all the assets present in the Asset Registry
|
| 573 |
+
|
| 574 |
+
Returns:
|
| 575 |
+
list[str]: Names of all the assets
|
| 576 |
+
"""
|
| 577 |
+
return self.client.call('simListAssets')
|
| 578 |
+
|
| 579 |
+
def simSpawnObject(self, object_name, asset_name, pose, scale, physics_enabled=False, is_blueprint=False):
|
| 580 |
+
"""Spawned selected object in the world
|
| 581 |
+
|
| 582 |
+
Args:
|
| 583 |
+
object_name (str): Desired name of new object
|
| 584 |
+
asset_name (str): Name of asset(mesh) in the project database
|
| 585 |
+
pose (airsim.Pose): Desired pose of object
|
| 586 |
+
scale (airsim.Vector3r): Desired scale of object
|
| 587 |
+
physics_enabled (bool, optional): Whether to enable physics for the object
|
| 588 |
+
is_blueprint (bool, optional): Whether to spawn a blueprint or an actor
|
| 589 |
+
|
| 590 |
+
Returns:
|
| 591 |
+
str: Name of spawned object, in case it had to be modified
|
| 592 |
+
"""
|
| 593 |
+
return self.client.call('simSpawnObject', object_name, asset_name, pose, scale, physics_enabled, is_blueprint)
|
| 594 |
+
|
| 595 |
+
def simDestroyObject(self, object_name):
|
| 596 |
+
"""Removes selected object from the world
|
| 597 |
+
|
| 598 |
+
Args:
|
| 599 |
+
object_name (str): Name of object to be removed
|
| 600 |
+
|
| 601 |
+
Returns:
|
| 602 |
+
bool: True if object is queued up for removal
|
| 603 |
+
"""
|
| 604 |
+
return self.client.call('simDestroyObject', object_name)
|
| 605 |
+
|
| 606 |
+
def simSetSegmentationObjectID(self, mesh_name, object_id, is_name_regex = False):
|
| 607 |
+
"""
|
| 608 |
+
Set segmentation ID for specific objects
|
| 609 |
+
|
| 610 |
+
See https://microsoft.github.io/AirSim/image_apis/#segmentation for details
|
| 611 |
+
|
| 612 |
+
Args:
|
| 613 |
+
mesh_name (str): Name of the mesh to set the ID of (supports regex)
|
| 614 |
+
object_id (int): Object ID to be set, range 0-255
|
| 615 |
+
|
| 616 |
+
RBG values for IDs can be seen at https://microsoft.github.io/AirSim/seg_rgbs.txt
|
| 617 |
+
is_name_regex (bool, optional): Whether the mesh name is a regex
|
| 618 |
+
|
| 619 |
+
Returns:
|
| 620 |
+
bool: If the mesh was found
|
| 621 |
+
"""
|
| 622 |
+
return self.client.call('simSetSegmentationObjectID', mesh_name, object_id, is_name_regex)
|
| 623 |
+
|
| 624 |
+
def simGetSegmentationObjectID(self, mesh_name):
|
| 625 |
+
"""
|
| 626 |
+
Returns Object ID for the given mesh name
|
| 627 |
+
|
| 628 |
+
Mapping of Object IDs to RGB values can be seen at https://microsoft.github.io/AirSim/seg_rgbs.txt
|
| 629 |
+
|
| 630 |
+
Args:
|
| 631 |
+
mesh_name (str): Name of the mesh to get the ID of
|
| 632 |
+
"""
|
| 633 |
+
return self.client.call('simGetSegmentationObjectID', mesh_name)
|
| 634 |
+
|
| 635 |
+
def simAddDetectionFilterMeshName(self, camera_name, image_type, mesh_name, vehicle_name = '', external = False):
|
| 636 |
+
"""
|
| 637 |
+
Add mesh name to detect in wild card format
|
| 638 |
+
|
| 639 |
+
For example: simAddDetectionFilterMeshName("Car_*") will detect all instance named "Car_*"
|
| 640 |
+
|
| 641 |
+
Args:
|
| 642 |
+
camera_name (str): Name of the camera, for backwards compatibility, ID numbers such as 0,1,etc. can also be used
|
| 643 |
+
image_type (ImageType): Type of image required
|
| 644 |
+
mesh_name (str): mesh name in wild card format
|
| 645 |
+
vehicle_name (str, optional): Vehicle which the camera is associated with
|
| 646 |
+
external (bool, optional): Whether the camera is an External Camera
|
| 647 |
+
|
| 648 |
+
"""
|
| 649 |
+
self.client.call('simAddDetectionFilterMeshName', camera_name, image_type, mesh_name, vehicle_name, external)
|
| 650 |
+
|
| 651 |
+
def simSetDetectionFilterRadius(self, camera_name, image_type, radius_cm, vehicle_name = '', external = False):
|
| 652 |
+
"""
|
| 653 |
+
Set detection radius for all cameras
|
| 654 |
+
|
| 655 |
+
Args:
|
| 656 |
+
camera_name (str): Name of the camera, for backwards compatibility, ID numbers such as 0,1,etc. can also be used
|
| 657 |
+
image_type (ImageType): Type of image required
|
| 658 |
+
radius_cm (int): Radius in [cm]
|
| 659 |
+
vehicle_name (str, optional): Vehicle which the camera is associated with
|
| 660 |
+
external (bool, optional): Whether the camera is an External Camera
|
| 661 |
+
"""
|
| 662 |
+
self.client.call('simSetDetectionFilterRadius', camera_name, image_type, radius_cm, vehicle_name, external)
|
| 663 |
+
|
| 664 |
+
def simClearDetectionMeshNames(self, camera_name, image_type, vehicle_name = '', external = False):
|
| 665 |
+
"""
|
| 666 |
+
Clear all mesh names from detection filter
|
| 667 |
+
|
| 668 |
+
Args:
|
| 669 |
+
camera_name (str): Name of the camera, for backwards compatibility, ID numbers such as 0,1,etc. can also be used
|
| 670 |
+
image_type (ImageType): Type of image required
|
| 671 |
+
vehicle_name (str, optional): Vehicle which the camera is associated with
|
| 672 |
+
external (bool, optional): Whether the camera is an External Camera
|
| 673 |
+
|
| 674 |
+
"""
|
| 675 |
+
self.client.call('simClearDetectionMeshNames', camera_name, image_type, vehicle_name, external)
|
| 676 |
+
|
| 677 |
+
def simGetDetections(self, camera_name, image_type, vehicle_name = '', external = False):
|
| 678 |
+
"""
|
| 679 |
+
Get current detections
|
| 680 |
+
|
| 681 |
+
Args:
|
| 682 |
+
camera_name (str): Name of the camera, for backwards compatibility, ID numbers such as 0,1,etc. can also be used
|
| 683 |
+
image_type (ImageType): Type of image required
|
| 684 |
+
vehicle_name (str, optional): Vehicle which the camera is associated with
|
| 685 |
+
external (bool, optional): Whether the camera is an External Camera
|
| 686 |
+
|
| 687 |
+
Returns:
|
| 688 |
+
DetectionInfo array
|
| 689 |
+
"""
|
| 690 |
+
responses_raw = self.client.call('simGetDetections', camera_name, image_type, vehicle_name, external)
|
| 691 |
+
return [DetectionInfo.from_msgpack(response_raw) for response_raw in responses_raw]
|
| 692 |
+
|
| 693 |
+
def simPrintLogMessage(self, message, message_param = "", severity = 0):
|
| 694 |
+
"""
|
| 695 |
+
Prints the specified message in the simulator's window.
|
| 696 |
+
|
| 697 |
+
If message_param is supplied, then it's printed next to the message and in that case if this API is called with same message value
|
| 698 |
+
but different message_param again then previous line is overwritten with new line (instead of API creating new line on display).
|
| 699 |
+
|
| 700 |
+
For example, `simPrintLogMessage("Iteration: ", to_string(i))` keeps updating same line on display when API is called with different values of i.
|
| 701 |
+
The valid values of severity parameter is 0 to 3 inclusive that corresponds to different colors.
|
| 702 |
+
|
| 703 |
+
Args:
|
| 704 |
+
message (str): Message to be printed
|
| 705 |
+
message_param (str, optional): Parameter to be printed next to the message
|
| 706 |
+
severity (int, optional): Range 0-3, inclusive, corresponding to the severity of the message
|
| 707 |
+
"""
|
| 708 |
+
self.client.call('simPrintLogMessage', message, message_param, severity)
|
| 709 |
+
|
| 710 |
+
def simGetCameraInfo(self, camera_name, vehicle_name = '', external=False):
|
| 711 |
+
"""
|
| 712 |
+
Get details about the camera
|
| 713 |
+
|
| 714 |
+
Args:
|
| 715 |
+
camera_name (str): Name of the camera, for backwards compatibility, ID numbers such as 0,1,etc. can also be used
|
| 716 |
+
vehicle_name (str, optional): Vehicle which the camera is associated with
|
| 717 |
+
external (bool, optional): Whether the camera is an External Camera
|
| 718 |
+
|
| 719 |
+
Returns:
|
| 720 |
+
CameraInfo:
|
| 721 |
+
"""
|
| 722 |
+
#TODO : below str() conversion is only needed for legacy reason and should be removed in future
|
| 723 |
+
return CameraInfo.from_msgpack(self.client.call('simGetCameraInfo', str(camera_name), vehicle_name, external))
|
| 724 |
+
|
| 725 |
+
def simGetDistortionParams(self, camera_name, vehicle_name = '', external = False):
|
| 726 |
+
"""
|
| 727 |
+
Get camera distortion parameters
|
| 728 |
+
|
| 729 |
+
Args:
|
| 730 |
+
camera_name (str): Name of the camera, for backwards compatibility, ID numbers such as 0,1,etc. can also be used
|
| 731 |
+
vehicle_name (str, optional): Vehicle which the camera is associated with
|
| 732 |
+
external (bool, optional): Whether the camera is an External Camera
|
| 733 |
+
|
| 734 |
+
Returns:
|
| 735 |
+
List (float): List of distortion parameter values corresponding to K1, K2, K3, P1, P2 respectively.
|
| 736 |
+
"""
|
| 737 |
+
|
| 738 |
+
return self.client.call('simGetDistortionParams', str(camera_name), vehicle_name, external)
|
| 739 |
+
|
| 740 |
+
def simSetDistortionParams(self, camera_name, distortion_params, vehicle_name = '', external = False):
|
| 741 |
+
"""
|
| 742 |
+
Set camera distortion parameters
|
| 743 |
+
|
| 744 |
+
Args:
|
| 745 |
+
camera_name (str): Name of the camera, for backwards compatibility, ID numbers such as 0,1,etc. can also be used
|
| 746 |
+
distortion_params (dict): Dictionary of distortion param names and corresponding values
|
| 747 |
+
{"K1": 0.0, "K2": 0.0, "K3": 0.0, "P1": 0.0, "P2": 0.0}
|
| 748 |
+
vehicle_name (str, optional): Vehicle which the camera is associated with
|
| 749 |
+
external (bool, optional): Whether the camera is an External Camera
|
| 750 |
+
"""
|
| 751 |
+
|
| 752 |
+
for param_name, value in distortion_params.items():
|
| 753 |
+
self.simSetDistortionParam(camera_name, param_name, value, vehicle_name, external)
|
| 754 |
+
|
| 755 |
+
def simSetDistortionParam(self, camera_name, param_name, value, vehicle_name = '', external = False):
|
| 756 |
+
"""
|
| 757 |
+
Set single camera distortion parameter
|
| 758 |
+
|
| 759 |
+
Args:
|
| 760 |
+
camera_name (str): Name of the camera, for backwards compatibility, ID numbers such as 0,1,etc. can also be used
|
| 761 |
+
param_name (str): Name of distortion parameter
|
| 762 |
+
value (float): Value of distortion parameter
|
| 763 |
+
vehicle_name (str, optional): Vehicle which the camera is associated with
|
| 764 |
+
external (bool, optional): Whether the camera is an External Camera
|
| 765 |
+
"""
|
| 766 |
+
self.client.call('simSetDistortionParam', str(camera_name), param_name, value, vehicle_name, external)
|
| 767 |
+
|
| 768 |
+
def simSetCameraPose(self, camera_name, pose, vehicle_name = '', external = False):
|
| 769 |
+
"""
|
| 770 |
+
- Control the pose of a selected camera
|
| 771 |
+
|
| 772 |
+
Args:
|
| 773 |
+
camera_name (str): Name of the camera to be controlled
|
| 774 |
+
pose (Pose): Pose representing the desired position and orientation of the camera
|
| 775 |
+
vehicle_name (str, optional): Name of vehicle which the camera corresponds to
|
| 776 |
+
external (bool, optional): Whether the camera is an External Camera
|
| 777 |
+
"""
|
| 778 |
+
#TODO : below str() conversion is only needed for legacy reason and should be removed in future
|
| 779 |
+
self.client.call('simSetCameraPose', str(camera_name), pose, vehicle_name, external)
|
| 780 |
+
|
| 781 |
+
def simSetCameraFov(self, camera_name, fov_degrees, vehicle_name = '', external = False):
|
| 782 |
+
"""
|
| 783 |
+
- Control the field of view of a selected camera
|
| 784 |
+
|
| 785 |
+
Args:
|
| 786 |
+
camera_name (str): Name of the camera to be controlled
|
| 787 |
+
fov_degrees (float): Value of field of view in degrees
|
| 788 |
+
vehicle_name (str, optional): Name of vehicle which the camera corresponds to
|
| 789 |
+
external (bool, optional): Whether the camera is an External Camera
|
| 790 |
+
"""
|
| 791 |
+
#TODO : below str() conversion is only needed for legacy reason and should be removed in future
|
| 792 |
+
self.client.call('simSetCameraFov', str(camera_name), fov_degrees, vehicle_name, external)
|
| 793 |
+
|
| 794 |
+
def simGetGroundTruthKinematics(self, vehicle_name = ''):
|
| 795 |
+
"""
|
| 796 |
+
Get Ground truth kinematics of the vehicle
|
| 797 |
+
|
| 798 |
+
The position inside the returned KinematicsState is in the frame of the vehicle's starting point
|
| 799 |
+
|
| 800 |
+
Args:
|
| 801 |
+
vehicle_name (str, optional): Name of the vehicle
|
| 802 |
+
|
| 803 |
+
Returns:
|
| 804 |
+
KinematicsState: Ground truth of the vehicle
|
| 805 |
+
"""
|
| 806 |
+
kinematics_state = self.client.call('simGetGroundTruthKinematics', vehicle_name)
|
| 807 |
+
return KinematicsState.from_msgpack(kinematics_state)
|
| 808 |
+
simGetGroundTruthKinematics.__annotations__ = {'return': KinematicsState}
|
| 809 |
+
|
| 810 |
+
def simSetKinematics(self, state, ignore_collision, vehicle_name = ''):
|
| 811 |
+
"""
|
| 812 |
+
Set the kinematics state of the vehicle
|
| 813 |
+
|
| 814 |
+
If you don't want to change position (or orientation) then just set components of position (or orientation) to floating point nan values
|
| 815 |
+
|
| 816 |
+
Args:
|
| 817 |
+
state (KinematicsState): Desired Pose pf the vehicle
|
| 818 |
+
ignore_collision (bool): Whether to ignore any collision or not
|
| 819 |
+
vehicle_name (str, optional): Name of the vehicle to move
|
| 820 |
+
"""
|
| 821 |
+
self.client.call('simSetKinematics', state, ignore_collision, vehicle_name)
|
| 822 |
+
|
| 823 |
+
def simGetGroundTruthEnvironment(self, vehicle_name = ''):
|
| 824 |
+
"""
|
| 825 |
+
Get ground truth environment state
|
| 826 |
+
|
| 827 |
+
The position inside the returned EnvironmentState is in the frame of the vehicle's starting point
|
| 828 |
+
|
| 829 |
+
Args:
|
| 830 |
+
vehicle_name (str, optional): Name of the vehicle
|
| 831 |
+
|
| 832 |
+
Returns:
|
| 833 |
+
EnvironmentState: Ground truth environment state
|
| 834 |
+
"""
|
| 835 |
+
env_state = self.client.call('simGetGroundTruthEnvironment', vehicle_name)
|
| 836 |
+
return EnvironmentState.from_msgpack(env_state)
|
| 837 |
+
simGetGroundTruthEnvironment.__annotations__ = {'return': EnvironmentState}
|
| 838 |
+
|
| 839 |
+
|
| 840 |
+
#sensor APIs
|
| 841 |
+
def getImuData(self, imu_name = '', vehicle_name = ''):
|
| 842 |
+
"""
|
| 843 |
+
Args:
|
| 844 |
+
imu_name (str, optional): Name of IMU to get data from, specified in settings.json
|
| 845 |
+
vehicle_name (str, optional): Name of vehicle to which the sensor corresponds to
|
| 846 |
+
|
| 847 |
+
Returns:
|
| 848 |
+
ImuData:
|
| 849 |
+
"""
|
| 850 |
+
return ImuData.from_msgpack(self.client.call('getImuData', imu_name, vehicle_name))
|
| 851 |
+
|
| 852 |
+
def getBarometerData(self, barometer_name = '', vehicle_name = ''):
|
| 853 |
+
"""
|
| 854 |
+
Args:
|
| 855 |
+
barometer_name (str, optional): Name of Barometer to get data from, specified in settings.json
|
| 856 |
+
vehicle_name (str, optional): Name of vehicle to which the sensor corresponds to
|
| 857 |
+
|
| 858 |
+
Returns:
|
| 859 |
+
BarometerData:
|
| 860 |
+
"""
|
| 861 |
+
return BarometerData.from_msgpack(self.client.call('getBarometerData', barometer_name, vehicle_name))
|
| 862 |
+
|
| 863 |
+
def getMagnetometerData(self, magnetometer_name = '', vehicle_name = ''):
|
| 864 |
+
"""
|
| 865 |
+
Args:
|
| 866 |
+
magnetometer_name (str, optional): Name of Magnetometer to get data from, specified in settings.json
|
| 867 |
+
vehicle_name (str, optional): Name of vehicle to which the sensor corresponds to
|
| 868 |
+
|
| 869 |
+
Returns:
|
| 870 |
+
MagnetometerData:
|
| 871 |
+
"""
|
| 872 |
+
return MagnetometerData.from_msgpack(self.client.call('getMagnetometerData', magnetometer_name, vehicle_name))
|
| 873 |
+
|
| 874 |
+
def getGpsData(self, gps_name = '', vehicle_name = ''):
|
| 875 |
+
"""
|
| 876 |
+
Args:
|
| 877 |
+
gps_name (str, optional): Name of GPS to get data from, specified in settings.json
|
| 878 |
+
vehicle_name (str, optional): Name of vehicle to which the sensor corresponds to
|
| 879 |
+
|
| 880 |
+
Returns:
|
| 881 |
+
GpsData:
|
| 882 |
+
"""
|
| 883 |
+
return GpsData.from_msgpack(self.client.call('getGpsData', gps_name, vehicle_name))
|
| 884 |
+
|
| 885 |
+
def getDistanceSensorData(self, distance_sensor_name = '', vehicle_name = ''):
|
| 886 |
+
"""
|
| 887 |
+
Args:
|
| 888 |
+
distance_sensor_name (str, optional): Name of Distance Sensor to get data from, specified in settings.json
|
| 889 |
+
vehicle_name (str, optional): Name of vehicle to which the sensor corresponds to
|
| 890 |
+
|
| 891 |
+
Returns:
|
| 892 |
+
DistanceSensorData:
|
| 893 |
+
"""
|
| 894 |
+
return DistanceSensorData.from_msgpack(self.client.call('getDistanceSensorData', distance_sensor_name, vehicle_name))
|
| 895 |
+
|
| 896 |
+
def getLidarData(self, lidar_name = '', vehicle_name = ''):
|
| 897 |
+
"""
|
| 898 |
+
Args:
|
| 899 |
+
lidar_name (str, optional): Name of Lidar to get data from, specified in settings.json
|
| 900 |
+
vehicle_name (str, optional): Name of vehicle to which the sensor corresponds to
|
| 901 |
+
|
| 902 |
+
Returns:
|
| 903 |
+
LidarData:
|
| 904 |
+
"""
|
| 905 |
+
return LidarData.from_msgpack(self.client.call('getLidarData', lidar_name, vehicle_name))
|
| 906 |
+
|
| 907 |
+
def simGetLidarSegmentation(self, lidar_name = '', vehicle_name = ''):
|
| 908 |
+
"""
|
| 909 |
+
NOTE: Deprecated API, use `getLidarData()` API instead
|
| 910 |
+
Returns Segmentation ID of each point's collided object in the last Lidar update
|
| 911 |
+
|
| 912 |
+
Args:
|
| 913 |
+
lidar_name (str, optional): Name of Lidar sensor
|
| 914 |
+
vehicle_name (str, optional): Name of the vehicle wth the sensor
|
| 915 |
+
|
| 916 |
+
Returns:
|
| 917 |
+
list[int]: Segmentation IDs of the objects
|
| 918 |
+
"""
|
| 919 |
+
logging.warning("simGetLidarSegmentation API is deprecated, use getLidarData() API instead")
|
| 920 |
+
return self.getLidarData(lidar_name, vehicle_name).segmentation
|
| 921 |
+
|
| 922 |
+
#Plotting APIs
|
| 923 |
+
def simFlushPersistentMarkers(self):
|
| 924 |
+
"""
|
| 925 |
+
Clear any persistent markers - those plotted with setting `is_persistent=True` in the APIs below
|
| 926 |
+
"""
|
| 927 |
+
self.client.call('simFlushPersistentMarkers')
|
| 928 |
+
|
| 929 |
+
def simPlotPoints(self, points, color_rgba=[1.0, 0.0, 0.0, 1.0], size = 10.0, duration = -1.0, is_persistent = False):
|
| 930 |
+
"""
|
| 931 |
+
Plot a list of 3D points in World NED frame
|
| 932 |
+
|
| 933 |
+
Args:
|
| 934 |
+
points (list[Vector3r]): List of Vector3r objects
|
| 935 |
+
color_rgba (list, optional): desired RGBA values from 0.0 to 1.0
|
| 936 |
+
size (float, optional): Size of plotted point
|
| 937 |
+
duration (float, optional): Duration (seconds) to plot for
|
| 938 |
+
is_persistent (bool, optional): If set to True, the desired object will be plotted for infinite time.
|
| 939 |
+
"""
|
| 940 |
+
self.client.call('simPlotPoints', points, color_rgba, size, duration, is_persistent)
|
| 941 |
+
|
| 942 |
+
def simPlotLineStrip(self, points, color_rgba=[1.0, 0.0, 0.0, 1.0], thickness = 5.0, duration = -1.0, is_persistent = False):
|
| 943 |
+
"""
|
| 944 |
+
Plots a line strip in World NED frame, defined from points[0] to points[1], points[1] to points[2], ... , points[n-2] to points[n-1]
|
| 945 |
+
|
| 946 |
+
Args:
|
| 947 |
+
points (list[Vector3r]): List of 3D locations of line start and end points, specified as Vector3r objects
|
| 948 |
+
color_rgba (list, optional): desired RGBA values from 0.0 to 1.0
|
| 949 |
+
thickness (float, optional): Thickness of line
|
| 950 |
+
duration (float, optional): Duration (seconds) to plot for
|
| 951 |
+
is_persistent (bool, optional): If set to True, the desired object will be plotted for infinite time.
|
| 952 |
+
"""
|
| 953 |
+
self.client.call('simPlotLineStrip', points, color_rgba, thickness, duration, is_persistent)
|
| 954 |
+
|
| 955 |
+
def simPlotLineList(self, points, color_rgba=[1.0, 0.0, 0.0, 1.0], thickness = 5.0, duration = -1.0, is_persistent = False):
|
| 956 |
+
"""
|
| 957 |
+
Plots a line strip in World NED frame, defined from points[0] to points[1], points[2] to points[3], ... , points[n-2] to points[n-1]
|
| 958 |
+
|
| 959 |
+
Args:
|
| 960 |
+
points (list[Vector3r]): List of 3D locations of line start and end points, specified as Vector3r objects. Must be even
|
| 961 |
+
color_rgba (list, optional): desired RGBA values from 0.0 to 1.0
|
| 962 |
+
thickness (float, optional): Thickness of line
|
| 963 |
+
duration (float, optional): Duration (seconds) to plot for
|
| 964 |
+
is_persistent (bool, optional): If set to True, the desired object will be plotted for infinite time.
|
| 965 |
+
"""
|
| 966 |
+
self.client.call('simPlotLineList', points, color_rgba, thickness, duration, is_persistent)
|
| 967 |
+
|
| 968 |
+
def simPlotArrows(self, points_start, points_end, color_rgba=[1.0, 0.0, 0.0, 1.0], thickness = 5.0, arrow_size = 2.0, duration = -1.0, is_persistent = False):
|
| 969 |
+
"""
|
| 970 |
+
Plots a list of arrows in World NED frame, defined from points_start[0] to points_end[0], points_start[1] to points_end[1], ... , points_start[n-1] to points_end[n-1]
|
| 971 |
+
|
| 972 |
+
Args:
|
| 973 |
+
points_start (list[Vector3r]): List of 3D start positions of arrow start positions, specified as Vector3r objects
|
| 974 |
+
points_end (list[Vector3r]): List of 3D end positions of arrow start positions, specified as Vector3r objects
|
| 975 |
+
color_rgba (list, optional): desired RGBA values from 0.0 to 1.0
|
| 976 |
+
thickness (float, optional): Thickness of line
|
| 977 |
+
arrow_size (float, optional): Size of arrow head
|
| 978 |
+
duration (float, optional): Duration (seconds) to plot for
|
| 979 |
+
is_persistent (bool, optional): If set to True, the desired object will be plotted for infinite time.
|
| 980 |
+
"""
|
| 981 |
+
self.client.call('simPlotArrows', points_start, points_end, color_rgba, thickness, arrow_size, duration, is_persistent)
|
| 982 |
+
|
| 983 |
+
|
| 984 |
+
def simPlotStrings(self, strings, positions, scale = 5, color_rgba=[1.0, 0.0, 0.0, 1.0], duration = -1.0):
|
| 985 |
+
"""
|
| 986 |
+
Plots a list of strings at desired positions in World NED frame.
|
| 987 |
+
|
| 988 |
+
Args:
|
| 989 |
+
strings (list[String], optional): List of strings to plot
|
| 990 |
+
positions (list[Vector3r]): List of positions where the strings should be plotted. Should be in one-to-one correspondence with the strings' list
|
| 991 |
+
scale (float, optional): Font scale of transform name
|
| 992 |
+
color_rgba (list, optional): desired RGBA values from 0.0 to 1.0
|
| 993 |
+
duration (float, optional): Duration (seconds) to plot for
|
| 994 |
+
"""
|
| 995 |
+
self.client.call('simPlotStrings', strings, positions, scale, color_rgba, duration)
|
| 996 |
+
|
| 997 |
+
def simPlotTransforms(self, poses, scale = 5.0, thickness = 5.0, duration = -1.0, is_persistent = False):
|
| 998 |
+
"""
|
| 999 |
+
Plots a list of transforms in World NED frame.
|
| 1000 |
+
|
| 1001 |
+
Args:
|
| 1002 |
+
poses (list[Pose]): List of Pose objects representing the transforms to plot
|
| 1003 |
+
scale (float, optional): Length of transforms' axes
|
| 1004 |
+
thickness (float, optional): Thickness of transforms' axes
|
| 1005 |
+
duration (float, optional): Duration (seconds) to plot for
|
| 1006 |
+
is_persistent (bool, optional): If set to True, the desired object will be plotted for infinite time.
|
| 1007 |
+
"""
|
| 1008 |
+
self.client.call('simPlotTransforms', poses, scale, thickness, duration, is_persistent)
|
| 1009 |
+
|
| 1010 |
+
def simPlotTransformsWithNames(self, poses, names, tf_scale = 5.0, tf_thickness = 5.0, text_scale = 10.0, text_color_rgba = [1.0, 0.0, 0.0, 1.0], duration = -1.0):
|
| 1011 |
+
"""
|
| 1012 |
+
Plots a list of transforms with their names in World NED frame.
|
| 1013 |
+
|
| 1014 |
+
Args:
|
| 1015 |
+
poses (list[Pose]): List of Pose objects representing the transforms to plot
|
| 1016 |
+
names (list[string]): List of strings with one-to-one correspondence to list of poses
|
| 1017 |
+
tf_scale (float, optional): Length of transforms' axes
|
| 1018 |
+
tf_thickness (float, optional): Thickness of transforms' axes
|
| 1019 |
+
text_scale (float, optional): Font scale of transform name
|
| 1020 |
+
text_color_rgba (list, optional): desired RGBA values from 0.0 to 1.0 for the transform name
|
| 1021 |
+
duration (float, optional): Duration (seconds) to plot for
|
| 1022 |
+
"""
|
| 1023 |
+
self.client.call('simPlotTransformsWithNames', poses, names, tf_scale, tf_thickness, text_scale, text_color_rgba, duration)
|
| 1024 |
+
|
| 1025 |
+
def cancelLastTask(self, vehicle_name = ''):
|
| 1026 |
+
"""
|
| 1027 |
+
Cancel previous Async task
|
| 1028 |
+
|
| 1029 |
+
Args:
|
| 1030 |
+
vehicle_name (str, optional): Name of the vehicle
|
| 1031 |
+
"""
|
| 1032 |
+
self.client.call('cancelLastTask', vehicle_name)
|
| 1033 |
+
|
| 1034 |
+
#Recording APIs
|
| 1035 |
+
def startRecording(self):
|
| 1036 |
+
"""
|
| 1037 |
+
Start Recording
|
| 1038 |
+
|
| 1039 |
+
Recording will be done according to the settings
|
| 1040 |
+
"""
|
| 1041 |
+
self.client.call('startRecording')
|
| 1042 |
+
|
| 1043 |
+
def stopRecording(self):
|
| 1044 |
+
"""
|
| 1045 |
+
Stop Recording
|
| 1046 |
+
"""
|
| 1047 |
+
self.client.call('stopRecording')
|
| 1048 |
+
|
| 1049 |
+
def isRecording(self):
|
| 1050 |
+
"""
|
| 1051 |
+
Whether Recording is running or not
|
| 1052 |
+
|
| 1053 |
+
Returns:
|
| 1054 |
+
bool: True if Recording, else False
|
| 1055 |
+
"""
|
| 1056 |
+
return self.client.call('isRecording')
|
| 1057 |
+
|
| 1058 |
+
def simSetWind(self, wind):
|
| 1059 |
+
"""
|
| 1060 |
+
Set simulated wind, in World frame, NED direction, m/s
|
| 1061 |
+
|
| 1062 |
+
Args:
|
| 1063 |
+
wind (Vector3r): Wind, in World frame, NED direction, in m/s
|
| 1064 |
+
"""
|
| 1065 |
+
self.client.call('simSetWind', wind)
|
| 1066 |
+
|
| 1067 |
+
def simCreateVoxelGrid(self, position, x, y, z, res, of):
|
| 1068 |
+
"""
|
| 1069 |
+
Construct and save a binvox-formatted voxel grid of environment
|
| 1070 |
+
|
| 1071 |
+
Args:
|
| 1072 |
+
position (Vector3r): Position around which voxel grid is centered in m
|
| 1073 |
+
x, y, z (int): Size of each voxel grid dimension in m
|
| 1074 |
+
res (float): Resolution of voxel grid in m
|
| 1075 |
+
of (str): Name of output file to save voxel grid as
|
| 1076 |
+
|
| 1077 |
+
Returns:
|
| 1078 |
+
bool: True if output written to file successfully, else False
|
| 1079 |
+
"""
|
| 1080 |
+
return self.client.call('simCreateVoxelGrid', position, x, y, z, res, of)
|
| 1081 |
+
|
| 1082 |
+
#Add new vehicle via RPC
|
| 1083 |
+
def simAddVehicle(self, vehicle_name, vehicle_type, pose, pawn_path = ""):
|
| 1084 |
+
"""
|
| 1085 |
+
Create vehicle at runtime
|
| 1086 |
+
|
| 1087 |
+
Args:
|
| 1088 |
+
vehicle_name (str): Name of the vehicle being created
|
| 1089 |
+
vehicle_type (str): Type of vehicle, e.g. "simpleflight"
|
| 1090 |
+
pose (Pose): Initial pose of the vehicle
|
| 1091 |
+
pawn_path (str, optional): Vehicle blueprint path, default empty wbich uses the default blueprint for the vehicle type
|
| 1092 |
+
|
| 1093 |
+
Returns:
|
| 1094 |
+
bool: Whether vehicle was created
|
| 1095 |
+
"""
|
| 1096 |
+
return self.client.call('simAddVehicle', vehicle_name, vehicle_type, pose, pawn_path)
|
| 1097 |
+
|
| 1098 |
+
def listVehicles(self):
|
| 1099 |
+
"""
|
| 1100 |
+
Lists the names of current vehicles
|
| 1101 |
+
|
| 1102 |
+
Returns:
|
| 1103 |
+
list[str]: List containing names of all vehicles
|
| 1104 |
+
"""
|
| 1105 |
+
return self.client.call('listVehicles')
|
| 1106 |
+
|
| 1107 |
+
def getSettingsString(self):
|
| 1108 |
+
"""
|
| 1109 |
+
Fetch the settings text being used by AirSim
|
| 1110 |
+
|
| 1111 |
+
Returns:
|
| 1112 |
+
str: Settings text in JSON format
|
| 1113 |
+
"""
|
| 1114 |
+
return self.client.call('getSettingsString')
|
| 1115 |
+
|
| 1116 |
+
#----------------------------------- Multirotor APIs ---------------------------------------------
|
| 1117 |
+
class MultirotorClient(VehicleClient, object):
|
| 1118 |
+
def __init__(self, ip = "", port = 41451, timeout_value = 3600):
|
| 1119 |
+
super(MultirotorClient, self).__init__(ip, port, timeout_value)
|
| 1120 |
+
|
| 1121 |
+
def takeoffAsync(self, timeout_sec = 20, vehicle_name = ''):
|
| 1122 |
+
"""
|
| 1123 |
+
Takeoff vehicle to 3m above ground. Vehicle should not be moving when this API is used
|
| 1124 |
+
|
| 1125 |
+
Args:
|
| 1126 |
+
timeout_sec (int, optional): Timeout for the vehicle to reach desired altitude
|
| 1127 |
+
vehicle_name (str, optional): Name of the vehicle to send this command to
|
| 1128 |
+
|
| 1129 |
+
Returns:
|
| 1130 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1131 |
+
"""
|
| 1132 |
+
return self.client.call_async('takeoff', timeout_sec, vehicle_name)
|
| 1133 |
+
|
| 1134 |
+
def landAsync(self, timeout_sec = 60, vehicle_name = ''):
|
| 1135 |
+
"""
|
| 1136 |
+
Land the vehicle
|
| 1137 |
+
|
| 1138 |
+
Args:
|
| 1139 |
+
timeout_sec (int, optional): Timeout for the vehicle to land
|
| 1140 |
+
vehicle_name (str, optional): Name of the vehicle to send this command to
|
| 1141 |
+
|
| 1142 |
+
Returns:
|
| 1143 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1144 |
+
"""
|
| 1145 |
+
return self.client.call_async('land', timeout_sec, vehicle_name)
|
| 1146 |
+
|
| 1147 |
+
def goHomeAsync(self, timeout_sec = 3e+38, vehicle_name = ''):
|
| 1148 |
+
"""
|
| 1149 |
+
Return vehicle to Home i.e. Launch location
|
| 1150 |
+
|
| 1151 |
+
Args:
|
| 1152 |
+
timeout_sec (int, optional): Timeout for the vehicle to reach desired altitude
|
| 1153 |
+
vehicle_name (str, optional): Name of the vehicle to send this command to
|
| 1154 |
+
|
| 1155 |
+
Returns:
|
| 1156 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1157 |
+
"""
|
| 1158 |
+
return self.client.call_async('goHome', timeout_sec, vehicle_name)
|
| 1159 |
+
|
| 1160 |
+
#APIs for control
|
| 1161 |
+
def moveByVelocityBodyFrameAsync(self, vx, vy, vz, duration, drivetrain = DrivetrainType.MaxDegreeOfFreedom, yaw_mode = YawMode(), vehicle_name = ''):
|
| 1162 |
+
"""
|
| 1163 |
+
Args:
|
| 1164 |
+
vx (float): desired velocity in the X axis of the vehicle's local NED frame.
|
| 1165 |
+
vy (float): desired velocity in the Y axis of the vehicle's local NED frame.
|
| 1166 |
+
vz (float): desired velocity in the Z axis of the vehicle's local NED frame.
|
| 1167 |
+
duration (float): Desired amount of time (seconds), to send this command for
|
| 1168 |
+
drivetrain (DrivetrainType, optional):
|
| 1169 |
+
yaw_mode (YawMode, optional):
|
| 1170 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1171 |
+
|
| 1172 |
+
Returns:
|
| 1173 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1174 |
+
"""
|
| 1175 |
+
return self.client.call_async('moveByVelocityBodyFrame', vx, vy, vz, duration, drivetrain, yaw_mode, vehicle_name)
|
| 1176 |
+
|
| 1177 |
+
def moveByVelocityZBodyFrameAsync(self, vx, vy, z, duration, drivetrain = DrivetrainType.MaxDegreeOfFreedom, yaw_mode = YawMode(), vehicle_name = ''):
|
| 1178 |
+
"""
|
| 1179 |
+
Args:
|
| 1180 |
+
vx (float): desired velocity in the X axis of the vehicle's local NED frame
|
| 1181 |
+
vy (float): desired velocity in the Y axis of the vehicle's local NED frame
|
| 1182 |
+
z (float): desired Z value (in local NED frame of the vehicle)
|
| 1183 |
+
duration (float): Desired amount of time (seconds), to send this command for
|
| 1184 |
+
drivetrain (DrivetrainType, optional):
|
| 1185 |
+
yaw_mode (YawMode, optional):
|
| 1186 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1187 |
+
|
| 1188 |
+
Returns:
|
| 1189 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1190 |
+
"""
|
| 1191 |
+
|
| 1192 |
+
return self.client.call_async('moveByVelocityZBodyFrame', vx, vy, z, duration, drivetrain, yaw_mode, vehicle_name)
|
| 1193 |
+
|
| 1194 |
+
def moveByAngleZAsync(self, pitch, roll, z, yaw, duration, vehicle_name = ''):
|
| 1195 |
+
logging.warning("moveByAngleZAsync API is deprecated, use moveByRollPitchYawZAsync() API instead")
|
| 1196 |
+
return self.client.call_async('moveByRollPitchYawZ', roll, -pitch, -yaw, z, duration, vehicle_name)
|
| 1197 |
+
|
| 1198 |
+
def moveByAngleThrottleAsync(self, pitch, roll, throttle, yaw_rate, duration, vehicle_name = ''):
|
| 1199 |
+
logging.warning("moveByAngleThrottleAsync API is deprecated, use moveByRollPitchYawrateThrottleAsync() API instead")
|
| 1200 |
+
return self.client.call_async('moveByRollPitchYawrateThrottle', roll, -pitch, -yaw_rate, throttle, duration, vehicle_name)
|
| 1201 |
+
|
| 1202 |
+
def moveByVelocityAsync(self, vx, vy, vz, duration, drivetrain = DrivetrainType.MaxDegreeOfFreedom, yaw_mode = YawMode(), vehicle_name = ''):
|
| 1203 |
+
"""
|
| 1204 |
+
Args:
|
| 1205 |
+
vx (float): desired velocity in world (NED) X axis
|
| 1206 |
+
vy (float): desired velocity in world (NED) Y axis
|
| 1207 |
+
vz (float): desired velocity in world (NED) Z axis
|
| 1208 |
+
duration (float): Desired amount of time (seconds), to send this command for
|
| 1209 |
+
drivetrain (DrivetrainType, optional):
|
| 1210 |
+
yaw_mode (YawMode, optional):
|
| 1211 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1212 |
+
|
| 1213 |
+
Returns:
|
| 1214 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1215 |
+
"""
|
| 1216 |
+
return self.client.call_async('moveByVelocity', vx, vy, vz, duration, drivetrain, yaw_mode, vehicle_name)
|
| 1217 |
+
|
| 1218 |
+
def moveByVelocityZAsync(self, vx, vy, z, duration, drivetrain = DrivetrainType.MaxDegreeOfFreedom, yaw_mode = YawMode(), vehicle_name = ''):
|
| 1219 |
+
return self.client.call_async('moveByVelocityZ', vx, vy, z, duration, drivetrain, yaw_mode, vehicle_name)
|
| 1220 |
+
|
| 1221 |
+
def moveOnPathAsync(self, path, velocity, timeout_sec = 3e+38, drivetrain = DrivetrainType.MaxDegreeOfFreedom, yaw_mode = YawMode(),
|
| 1222 |
+
lookahead = -1, adaptive_lookahead = 1, vehicle_name = ''):
|
| 1223 |
+
return self.client.call_async('moveOnPath', path, velocity, timeout_sec, drivetrain, yaw_mode, lookahead, adaptive_lookahead, vehicle_name)
|
| 1224 |
+
|
| 1225 |
+
def moveToPositionAsync(self, x, y, z, velocity, timeout_sec = 3e+38, drivetrain = DrivetrainType.MaxDegreeOfFreedom, yaw_mode = YawMode(),
|
| 1226 |
+
lookahead = -1, adaptive_lookahead = 1, vehicle_name = ''):
|
| 1227 |
+
return self.client.call_async('moveToPosition', x, y, z, velocity, timeout_sec, drivetrain, yaw_mode, lookahead, adaptive_lookahead, vehicle_name)
|
| 1228 |
+
|
| 1229 |
+
def moveToGPSAsync(self, latitude, longitude, altitude, velocity, timeout_sec = 3e+38, drivetrain = DrivetrainType.MaxDegreeOfFreedom, yaw_mode = YawMode(),
|
| 1230 |
+
lookahead = -1, adaptive_lookahead = 1, vehicle_name = ''):
|
| 1231 |
+
return self.client.call_async('moveToGPS', latitude, longitude, altitude, velocity, timeout_sec, drivetrain, yaw_mode, lookahead, adaptive_lookahead, vehicle_name)
|
| 1232 |
+
|
| 1233 |
+
def moveToZAsync(self, z, velocity, timeout_sec = 3e+38, yaw_mode = YawMode(), lookahead = -1, adaptive_lookahead = 1, vehicle_name = ''):
|
| 1234 |
+
return self.client.call_async('moveToZ', z, velocity, timeout_sec, yaw_mode, lookahead, adaptive_lookahead, vehicle_name)
|
| 1235 |
+
|
| 1236 |
+
def moveByManualAsync(self, vx_max, vy_max, z_min, duration, drivetrain = DrivetrainType.MaxDegreeOfFreedom, yaw_mode = YawMode(), vehicle_name = ''):
|
| 1237 |
+
"""
|
| 1238 |
+
- Read current RC state and use it to control the vehicles.
|
| 1239 |
+
|
| 1240 |
+
Parameters sets up the constraints on velocity and minimum altitude while flying. If RC state is detected to violate these constraints
|
| 1241 |
+
then that RC state would be ignored.
|
| 1242 |
+
|
| 1243 |
+
Args:
|
| 1244 |
+
vx_max (float): max velocity allowed in x direction
|
| 1245 |
+
vy_max (float): max velocity allowed in y direction
|
| 1246 |
+
vz_max (float): max velocity allowed in z direction
|
| 1247 |
+
z_min (float): min z allowed for vehicle position
|
| 1248 |
+
duration (float): after this duration vehicle would switch back to non-manual mode
|
| 1249 |
+
drivetrain (DrivetrainType): when ForwardOnly, vehicle rotates itself so that its front is always facing the direction of travel. If MaxDegreeOfFreedom then it doesn't do that (crab-like movement)
|
| 1250 |
+
yaw_mode (YawMode): Specifies if vehicle should face at given angle (is_rate=False) or should be rotating around its axis at given rate (is_rate=True)
|
| 1251 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1252 |
+
Returns:
|
| 1253 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1254 |
+
"""
|
| 1255 |
+
return self.client.call_async('moveByManual', vx_max, vy_max, z_min, duration, drivetrain, yaw_mode, vehicle_name)
|
| 1256 |
+
|
| 1257 |
+
def rotateToYawAsync(self, yaw, timeout_sec = 3e+38, margin = 5, vehicle_name = ''):
|
| 1258 |
+
return self.client.call_async('rotateToYaw', yaw, timeout_sec, margin, vehicle_name)
|
| 1259 |
+
|
| 1260 |
+
def rotateByYawRateAsync(self, yaw_rate, duration, vehicle_name = ''):
|
| 1261 |
+
return self.client.call_async('rotateByYawRate', yaw_rate, duration, vehicle_name)
|
| 1262 |
+
|
| 1263 |
+
def hoverAsync(self, vehicle_name = ''):
|
| 1264 |
+
return self.client.call_async('hover', vehicle_name)
|
| 1265 |
+
|
| 1266 |
+
def moveByRC(self, rcdata = RCData(), vehicle_name = ''):
|
| 1267 |
+
return self.client.call('moveByRC', rcdata, vehicle_name)
|
| 1268 |
+
|
| 1269 |
+
#low - level control API
|
| 1270 |
+
def moveByMotorPWMsAsync(self, front_right_pwm, rear_left_pwm, front_left_pwm, rear_right_pwm, duration, vehicle_name = ''):
|
| 1271 |
+
"""
|
| 1272 |
+
- Directly control the motors using PWM values
|
| 1273 |
+
|
| 1274 |
+
Args:
|
| 1275 |
+
front_right_pwm (float): PWM value for the front right motor (between 0.0 to 1.0)
|
| 1276 |
+
rear_left_pwm (float): PWM value for the rear left motor (between 0.0 to 1.0)
|
| 1277 |
+
front_left_pwm (float): PWM value for the front left motor (between 0.0 to 1.0)
|
| 1278 |
+
rear_right_pwm (float): PWM value for the rear right motor (between 0.0 to 1.0)
|
| 1279 |
+
duration (float): Desired amount of time (seconds), to send this command for
|
| 1280 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1281 |
+
Returns:
|
| 1282 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1283 |
+
"""
|
| 1284 |
+
return self.client.call_async('moveByMotorPWMs', front_right_pwm, rear_left_pwm, front_left_pwm, rear_right_pwm, duration, vehicle_name)
|
| 1285 |
+
|
| 1286 |
+
def moveByRollPitchYawZAsync(self, roll, pitch, yaw, z, duration, vehicle_name = ''):
|
| 1287 |
+
"""
|
| 1288 |
+
- z is given in local NED frame of the vehicle.
|
| 1289 |
+
- Roll angle, pitch angle, and yaw angle set points are given in **radians**, in the body frame.
|
| 1290 |
+
- The body frame follows the Front Left Up (FLU) convention, and right-handedness.
|
| 1291 |
+
|
| 1292 |
+
- Frame Convention:
|
| 1293 |
+
- X axis is along the **Front** direction of the quadrotor.
|
| 1294 |
+
|
| 1295 |
+
| Clockwise rotation about this axis defines a positive **roll** angle.
|
| 1296 |
+
| Hence, rolling with a positive angle is equivalent to translating in the **right** direction, w.r.t. our FLU body frame.
|
| 1297 |
+
|
| 1298 |
+
- Y axis is along the **Left** direction of the quadrotor.
|
| 1299 |
+
|
| 1300 |
+
| Clockwise rotation about this axis defines a positive **pitch** angle.
|
| 1301 |
+
| Hence, pitching with a positive angle is equivalent to translating in the **front** direction, w.r.t. our FLU body frame.
|
| 1302 |
+
|
| 1303 |
+
- Z axis is along the **Up** direction.
|
| 1304 |
+
|
| 1305 |
+
| Clockwise rotation about this axis defines a positive **yaw** angle.
|
| 1306 |
+
| Hence, yawing with a positive angle is equivalent to rotated towards the **left** direction wrt our FLU body frame. Or in an anticlockwise fashion in the body XY / FL plane.
|
| 1307 |
+
|
| 1308 |
+
Args:
|
| 1309 |
+
roll (float): Desired roll angle, in radians.
|
| 1310 |
+
pitch (float): Desired pitch angle, in radians.
|
| 1311 |
+
yaw (float): Desired yaw angle, in radians.
|
| 1312 |
+
z (float): Desired Z value (in local NED frame of the vehicle)
|
| 1313 |
+
duration (float): Desired amount of time (seconds), to send this command for
|
| 1314 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1315 |
+
|
| 1316 |
+
Returns:
|
| 1317 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1318 |
+
"""
|
| 1319 |
+
return self.client.call_async('moveByRollPitchYawZ', roll, -pitch, -yaw, z, duration, vehicle_name)
|
| 1320 |
+
|
| 1321 |
+
def moveByRollPitchYawThrottleAsync(self, roll, pitch, yaw, throttle, duration, vehicle_name = ''):
|
| 1322 |
+
"""
|
| 1323 |
+
- Desired throttle is between 0.0 to 1.0
|
| 1324 |
+
- Roll angle, pitch angle, and yaw angle are given in **degrees** when using PX4 and in **radians** when using SimpleFlight, in the body frame.
|
| 1325 |
+
- The body frame follows the Front Left Up (FLU) convention, and right-handedness.
|
| 1326 |
+
|
| 1327 |
+
- Frame Convention:
|
| 1328 |
+
- X axis is along the **Front** direction of the quadrotor.
|
| 1329 |
+
|
| 1330 |
+
| Clockwise rotation about this axis defines a positive **roll** angle.
|
| 1331 |
+
| Hence, rolling with a positive angle is equivalent to translating in the **right** direction, w.r.t. our FLU body frame.
|
| 1332 |
+
|
| 1333 |
+
- Y axis is along the **Left** direction of the quadrotor.
|
| 1334 |
+
|
| 1335 |
+
| Clockwise rotation about this axis defines a positive **pitch** angle.
|
| 1336 |
+
| Hence, pitching with a positive angle is equivalent to translating in the **front** direction, w.r.t. our FLU body frame.
|
| 1337 |
+
|
| 1338 |
+
- Z axis is along the **Up** direction.
|
| 1339 |
+
|
| 1340 |
+
| Clockwise rotation about this axis defines a positive **yaw** angle.
|
| 1341 |
+
| Hence, yawing with a positive angle is equivalent to rotated towards the **left** direction wrt our FLU body frame. Or in an anticlockwise fashion in the body XY / FL plane.
|
| 1342 |
+
|
| 1343 |
+
Args:
|
| 1344 |
+
roll (float): Desired roll angle.
|
| 1345 |
+
pitch (float): Desired pitch angle.
|
| 1346 |
+
yaw (float): Desired yaw angle.
|
| 1347 |
+
throttle (float): Desired throttle (between 0.0 to 1.0)
|
| 1348 |
+
duration (float): Desired amount of time (seconds), to send this command for
|
| 1349 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1350 |
+
|
| 1351 |
+
Returns:
|
| 1352 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1353 |
+
"""
|
| 1354 |
+
return self.client.call_async('moveByRollPitchYawThrottle', roll, -pitch, -yaw, throttle, duration, vehicle_name)
|
| 1355 |
+
|
| 1356 |
+
def moveByRollPitchYawrateThrottleAsync(self, roll, pitch, yaw_rate, throttle, duration, vehicle_name = ''):
|
| 1357 |
+
"""
|
| 1358 |
+
- Desired throttle is between 0.0 to 1.0
|
| 1359 |
+
- Roll angle, pitch angle, and yaw rate set points are given in **radians**, in the body frame.
|
| 1360 |
+
- The body frame follows the Front Left Up (FLU) convention, and right-handedness.
|
| 1361 |
+
|
| 1362 |
+
- Frame Convention:
|
| 1363 |
+
- X axis is along the **Front** direction of the quadrotor.
|
| 1364 |
+
|
| 1365 |
+
| Clockwise rotation about this axis defines a positive **roll** angle.
|
| 1366 |
+
| Hence, rolling with a positive angle is equivalent to translating in the **right** direction, w.r.t. our FLU body frame.
|
| 1367 |
+
|
| 1368 |
+
- Y axis is along the **Left** direction of the quadrotor.
|
| 1369 |
+
|
| 1370 |
+
| Clockwise rotation about this axis defines a positive **pitch** angle.
|
| 1371 |
+
| Hence, pitching with a positive angle is equivalent to translating in the **front** direction, w.r.t. our FLU body frame.
|
| 1372 |
+
|
| 1373 |
+
- Z axis is along the **Up** direction.
|
| 1374 |
+
|
| 1375 |
+
| Clockwise rotation about this axis defines a positive **yaw** angle.
|
| 1376 |
+
| Hence, yawing with a positive angle is equivalent to rotated towards the **left** direction wrt our FLU body frame. Or in an anticlockwise fashion in the body XY / FL plane.
|
| 1377 |
+
|
| 1378 |
+
Args:
|
| 1379 |
+
roll (float): Desired roll angle, in radians.
|
| 1380 |
+
pitch (float): Desired pitch angle, in radians.
|
| 1381 |
+
yaw_rate (float): Desired yaw rate, in radian per second.
|
| 1382 |
+
throttle (float): Desired throttle (between 0.0 to 1.0)
|
| 1383 |
+
duration (float): Desired amount of time (seconds), to send this command for
|
| 1384 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1385 |
+
|
| 1386 |
+
Returns:
|
| 1387 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1388 |
+
"""
|
| 1389 |
+
return self.client.call_async('moveByRollPitchYawrateThrottle', roll, -pitch, -yaw_rate, throttle, duration, vehicle_name)
|
| 1390 |
+
|
| 1391 |
+
def moveByRollPitchYawrateZAsync(self, roll, pitch, yaw_rate, z, duration, vehicle_name = ''):
|
| 1392 |
+
"""
|
| 1393 |
+
- z is given in local NED frame of the vehicle.
|
| 1394 |
+
- Roll angle, pitch angle, and yaw rate set points are given in **radians**, in the body frame.
|
| 1395 |
+
- The body frame follows the Front Left Up (FLU) convention, and right-handedness.
|
| 1396 |
+
|
| 1397 |
+
- Frame Convention:
|
| 1398 |
+
- X axis is along the **Front** direction of the quadrotor.
|
| 1399 |
+
|
| 1400 |
+
| Clockwise rotation about this axis defines a positive **roll** angle.
|
| 1401 |
+
| Hence, rolling with a positive angle is equivalent to translating in the **right** direction, w.r.t. our FLU body frame.
|
| 1402 |
+
|
| 1403 |
+
- Y axis is along the **Left** direction of the quadrotor.
|
| 1404 |
+
|
| 1405 |
+
| Clockwise rotation about this axis defines a positive **pitch** angle.
|
| 1406 |
+
| Hence, pitching with a positive angle is equivalent to translating in the **front** direction, w.r.t. our FLU body frame.
|
| 1407 |
+
|
| 1408 |
+
- Z axis is along the **Up** direction.
|
| 1409 |
+
|
| 1410 |
+
| Clockwise rotation about this axis defines a positive **yaw** angle.
|
| 1411 |
+
| Hence, yawing with a positive angle is equivalent to rotated towards the **left** direction wrt our FLU body frame. Or in an anticlockwise fashion in the body XY / FL plane.
|
| 1412 |
+
|
| 1413 |
+
Args:
|
| 1414 |
+
roll (float): Desired roll angle, in radians.
|
| 1415 |
+
pitch (float): Desired pitch angle, in radians.
|
| 1416 |
+
yaw_rate (float): Desired yaw rate, in radian per second.
|
| 1417 |
+
z (float): Desired Z value (in local NED frame of the vehicle)
|
| 1418 |
+
duration (float): Desired amount of time (seconds), to send this command for
|
| 1419 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1420 |
+
|
| 1421 |
+
Returns:
|
| 1422 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1423 |
+
"""
|
| 1424 |
+
return self.client.call_async('moveByRollPitchYawrateZ', roll, -pitch, -yaw_rate, z, duration, vehicle_name)
|
| 1425 |
+
|
| 1426 |
+
def moveByAngleRatesZAsync(self, roll_rate, pitch_rate, yaw_rate, z, duration, vehicle_name = ''):
|
| 1427 |
+
"""
|
| 1428 |
+
- z is given in local NED frame of the vehicle.
|
| 1429 |
+
- Roll rate, pitch rate, and yaw rate set points are given in **radians**, in the body frame.
|
| 1430 |
+
- The body frame follows the Front Left Up (FLU) convention, and right-handedness.
|
| 1431 |
+
|
| 1432 |
+
- Frame Convention:
|
| 1433 |
+
- X axis is along the **Front** direction of the quadrotor.
|
| 1434 |
+
|
| 1435 |
+
| Clockwise rotation about this axis defines a positive **roll** angle.
|
| 1436 |
+
| Hence, rolling with a positive angle is equivalent to translating in the **right** direction, w.r.t. our FLU body frame.
|
| 1437 |
+
|
| 1438 |
+
- Y axis is along the **Left** direction of the quadrotor.
|
| 1439 |
+
|
| 1440 |
+
| Clockwise rotation about this axis defines a positive **pitch** angle.
|
| 1441 |
+
| Hence, pitching with a positive angle is equivalent to translating in the **front** direction, w.r.t. our FLU body frame.
|
| 1442 |
+
|
| 1443 |
+
- Z axis is along the **Up** direction.
|
| 1444 |
+
|
| 1445 |
+
| Clockwise rotation about this axis defines a positive **yaw** angle.
|
| 1446 |
+
| Hence, yawing with a positive angle is equivalent to rotated towards the **left** direction wrt our FLU body frame. Or in an anticlockwise fashion in the body XY / FL plane.
|
| 1447 |
+
|
| 1448 |
+
Args:
|
| 1449 |
+
roll_rate (float): Desired roll rate, in radians / second
|
| 1450 |
+
pitch_rate (float): Desired pitch rate, in radians / second
|
| 1451 |
+
yaw_rate (float): Desired yaw rate, in radians / second
|
| 1452 |
+
z (float): Desired Z value (in local NED frame of the vehicle)
|
| 1453 |
+
duration (float): Desired amount of time (seconds), to send this command for
|
| 1454 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1455 |
+
|
| 1456 |
+
Returns:
|
| 1457 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1458 |
+
"""
|
| 1459 |
+
return self.client.call_async('moveByAngleRatesZ', roll_rate, -pitch_rate, -yaw_rate, z, duration, vehicle_name)
|
| 1460 |
+
|
| 1461 |
+
def moveByAngleRatesThrottleAsync(self, roll_rate, pitch_rate, yaw_rate, throttle, duration, vehicle_name = ''):
|
| 1462 |
+
"""
|
| 1463 |
+
- Desired throttle is between 0.0 to 1.0
|
| 1464 |
+
- Roll rate, pitch rate, and yaw rate set points are given in **radians**, in the body frame.
|
| 1465 |
+
- The body frame follows the Front Left Up (FLU) convention, and right-handedness.
|
| 1466 |
+
|
| 1467 |
+
- Frame Convention:
|
| 1468 |
+
- X axis is along the **Front** direction of the quadrotor.
|
| 1469 |
+
|
| 1470 |
+
| Clockwise rotation about this axis defines a positive **roll** angle.
|
| 1471 |
+
| Hence, rolling with a positive angle is equivalent to translating in the **right** direction, w.r.t. our FLU body frame.
|
| 1472 |
+
|
| 1473 |
+
- Y axis is along the **Left** direction of the quadrotor.
|
| 1474 |
+
|
| 1475 |
+
| Clockwise rotation about this axis defines a positive **pitch** angle.
|
| 1476 |
+
| Hence, pitching with a positive angle is equivalent to translating in the **front** direction, w.r.t. our FLU body frame.
|
| 1477 |
+
|
| 1478 |
+
- Z axis is along the **Up** direction.
|
| 1479 |
+
|
| 1480 |
+
| Clockwise rotation about this axis defines a positive **yaw** angle.
|
| 1481 |
+
| Hence, yawing with a positive angle is equivalent to rotated towards the **left** direction wrt our FLU body frame. Or in an anticlockwise fashion in the body XY / FL plane.
|
| 1482 |
+
|
| 1483 |
+
Args:
|
| 1484 |
+
roll_rate (float): Desired roll rate, in radians / second
|
| 1485 |
+
pitch_rate (float): Desired pitch rate, in radians / second
|
| 1486 |
+
yaw_rate (float): Desired yaw rate, in radians / second
|
| 1487 |
+
throttle (float): Desired throttle (between 0.0 to 1.0)
|
| 1488 |
+
duration (float): Desired amount of time (seconds), to send this command for
|
| 1489 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1490 |
+
|
| 1491 |
+
Returns:
|
| 1492 |
+
msgpackrpc.future.Future: future. call .join() to wait for method to finish. Example: client.METHOD().join()
|
| 1493 |
+
"""
|
| 1494 |
+
return self.client.call_async('moveByAngleRatesThrottle', roll_rate, -pitch_rate, -yaw_rate, throttle, duration, vehicle_name)
|
| 1495 |
+
|
| 1496 |
+
def setAngleRateControllerGains(self, angle_rate_gains=AngleRateControllerGains(), vehicle_name = ''):
|
| 1497 |
+
"""
|
| 1498 |
+
- Modifying these gains will have an affect on *ALL* move*() APIs.
|
| 1499 |
+
This is because any velocity setpoint is converted to an angle level setpoint which is tracked with an angle level controllers.
|
| 1500 |
+
That angle level setpoint is itself tracked with and angle rate controller.
|
| 1501 |
+
- This function should only be called if the default angle rate control PID gains need to be modified.
|
| 1502 |
+
|
| 1503 |
+
Args:
|
| 1504 |
+
angle_rate_gains (AngleRateControllerGains):
|
| 1505 |
+
- Correspond to the roll, pitch, yaw axes, defined in the body frame.
|
| 1506 |
+
- Pass AngleRateControllerGains() to reset gains to default recommended values.
|
| 1507 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1508 |
+
"""
|
| 1509 |
+
self.client.call('setAngleRateControllerGains', *(angle_rate_gains.to_lists()+(vehicle_name,)))
|
| 1510 |
+
|
| 1511 |
+
def setAngleLevelControllerGains(self, angle_level_gains=AngleLevelControllerGains(), vehicle_name = ''):
|
| 1512 |
+
"""
|
| 1513 |
+
- Sets angle level controller gains (used by any API setting angle references - for ex: moveByRollPitchYawZAsync(), moveByRollPitchYawThrottleAsync(), etc)
|
| 1514 |
+
- Modifying these gains will also affect the behaviour of moveByVelocityAsync() API.
|
| 1515 |
+
This is because the AirSim flight controller will track velocity setpoints by converting them to angle set points.
|
| 1516 |
+
- This function should only be called if the default angle level control PID gains need to be modified.
|
| 1517 |
+
- Passing AngleLevelControllerGains() sets gains to default airsim values.
|
| 1518 |
+
|
| 1519 |
+
Args:
|
| 1520 |
+
angle_level_gains (AngleLevelControllerGains):
|
| 1521 |
+
- Correspond to the roll, pitch, yaw axes, defined in the body frame.
|
| 1522 |
+
- Pass AngleLevelControllerGains() to reset gains to default recommended values.
|
| 1523 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1524 |
+
"""
|
| 1525 |
+
self.client.call('setAngleLevelControllerGains', *(angle_level_gains.to_lists()+(vehicle_name,)))
|
| 1526 |
+
|
| 1527 |
+
def setVelocityControllerGains(self, velocity_gains=VelocityControllerGains(), vehicle_name = ''):
|
| 1528 |
+
"""
|
| 1529 |
+
- Sets velocity controller gains for moveByVelocityAsync().
|
| 1530 |
+
- This function should only be called if the default velocity control PID gains need to be modified.
|
| 1531 |
+
- Passing VelocityControllerGains() sets gains to default airsim values.
|
| 1532 |
+
|
| 1533 |
+
Args:
|
| 1534 |
+
velocity_gains (VelocityControllerGains):
|
| 1535 |
+
- Correspond to the world X, Y, Z axes.
|
| 1536 |
+
- Pass VelocityControllerGains() to reset gains to default recommended values.
|
| 1537 |
+
- Modifying velocity controller gains will have an affect on the behaviour of moveOnSplineAsync() and moveOnSplineVelConstraintsAsync(), as they both use velocity control to track the trajectory.
|
| 1538 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1539 |
+
"""
|
| 1540 |
+
self.client.call('setVelocityControllerGains', *(velocity_gains.to_lists()+(vehicle_name,)))
|
| 1541 |
+
|
| 1542 |
+
|
| 1543 |
+
def setPositionControllerGains(self, position_gains=PositionControllerGains(), vehicle_name = ''):
|
| 1544 |
+
"""
|
| 1545 |
+
Sets position controller gains for moveByPositionAsync.
|
| 1546 |
+
This function should only be called if the default position control PID gains need to be modified.
|
| 1547 |
+
|
| 1548 |
+
Args:
|
| 1549 |
+
position_gains (PositionControllerGains):
|
| 1550 |
+
- Correspond to the X, Y, Z axes.
|
| 1551 |
+
- Pass PositionControllerGains() to reset gains to default recommended values.
|
| 1552 |
+
vehicle_name (str, optional): Name of the multirotor to send this command to
|
| 1553 |
+
"""
|
| 1554 |
+
self.client.call('setPositionControllerGains', *(position_gains.to_lists()+(vehicle_name,)))
|
| 1555 |
+
|
| 1556 |
+
#query vehicle state
|
| 1557 |
+
def getMultirotorState(self, vehicle_name = ''):
|
| 1558 |
+
"""
|
| 1559 |
+
The position inside the returned MultirotorState is in the frame of the vehicle's starting point
|
| 1560 |
+
|
| 1561 |
+
Args:
|
| 1562 |
+
vehicle_name (str, optional): Vehicle to get the state of
|
| 1563 |
+
|
| 1564 |
+
Returns:
|
| 1565 |
+
MultirotorState:
|
| 1566 |
+
"""
|
| 1567 |
+
return MultirotorState.from_msgpack(self.client.call('getMultirotorState', vehicle_name))
|
| 1568 |
+
getMultirotorState.__annotations__ = {'return': MultirotorState}
|
| 1569 |
+
#query rotor states
|
| 1570 |
+
def getRotorStates(self, vehicle_name = ''):
|
| 1571 |
+
"""
|
| 1572 |
+
Used to obtain the current state of all a multirotor's rotors. The state includes the speeds,
|
| 1573 |
+
thrusts and torques for all rotors.
|
| 1574 |
+
|
| 1575 |
+
Args:
|
| 1576 |
+
vehicle_name (str, optional): Vehicle to get the rotor state of
|
| 1577 |
+
|
| 1578 |
+
Returns:
|
| 1579 |
+
RotorStates: Containing a timestamp and the speed, thrust and torque of all rotors.
|
| 1580 |
+
"""
|
| 1581 |
+
return RotorStates.from_msgpack(self.client.call('getRotorStates', vehicle_name))
|
| 1582 |
+
getRotorStates.__annotations__ = {'return': RotorStates}
|
| 1583 |
+
|
| 1584 |
+
#----------------------------------- Car APIs ---------------------------------------------
|
| 1585 |
+
class CarClient(VehicleClient, object):
|
| 1586 |
+
def __init__(self, ip = "", port = 41451, timeout_value = 3600):
|
| 1587 |
+
super(CarClient, self).__init__(ip, port, timeout_value)
|
| 1588 |
+
|
| 1589 |
+
def setCarControls(self, controls, vehicle_name = ''):
|
| 1590 |
+
"""
|
| 1591 |
+
Control the car using throttle, steering, brake, etc.
|
| 1592 |
+
|
| 1593 |
+
Args:
|
| 1594 |
+
controls (CarControls): Struct containing control values
|
| 1595 |
+
vehicle_name (str, optional): Name of vehicle to be controlled
|
| 1596 |
+
"""
|
| 1597 |
+
self.client.call('setCarControls', controls, vehicle_name)
|
| 1598 |
+
|
| 1599 |
+
def getCarState(self, vehicle_name = ''):
|
| 1600 |
+
"""
|
| 1601 |
+
The position inside the returned CarState is in the frame of the vehicle's starting point
|
| 1602 |
+
|
| 1603 |
+
Args:
|
| 1604 |
+
vehicle_name (str, optional): Name of vehicle
|
| 1605 |
+
|
| 1606 |
+
Returns:
|
| 1607 |
+
CarState:
|
| 1608 |
+
"""
|
| 1609 |
+
state_raw = self.client.call('getCarState', vehicle_name)
|
| 1610 |
+
return CarState.from_msgpack(state_raw)
|
| 1611 |
+
|
| 1612 |
+
def getCarControls(self, vehicle_name=''):
|
| 1613 |
+
"""
|
| 1614 |
+
Args:
|
| 1615 |
+
vehicle_name (str, optional): Name of vehicle
|
| 1616 |
+
|
| 1617 |
+
Returns:
|
| 1618 |
+
CarControls:
|
| 1619 |
+
"""
|
| 1620 |
+
controls_raw = self.client.call('getCarControls', vehicle_name)
|
| 1621 |
+
return CarControls.from_msgpack(controls_raw)
|
airsim/pfm.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import matplotlib.pyplot as plt
|
| 3 |
+
import re
|
| 4 |
+
import sys
|
| 5 |
+
import pdb
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def read_pfm(file):
|
| 9 |
+
""" Read a pfm file """
|
| 10 |
+
file = open(file, 'rb')
|
| 11 |
+
|
| 12 |
+
color = None
|
| 13 |
+
width = None
|
| 14 |
+
height = None
|
| 15 |
+
scale = None
|
| 16 |
+
endian = None
|
| 17 |
+
|
| 18 |
+
header = file.readline().rstrip()
|
| 19 |
+
header = str(bytes.decode(header, encoding='utf-8'))
|
| 20 |
+
if header == 'PF':
|
| 21 |
+
color = True
|
| 22 |
+
elif header == 'Pf':
|
| 23 |
+
color = False
|
| 24 |
+
else:
|
| 25 |
+
raise Exception('Not a PFM file.')
|
| 26 |
+
|
| 27 |
+
pattern = r'^(\d+)\s(\d+)\s$'
|
| 28 |
+
temp_str = str(bytes.decode(file.readline(), encoding='utf-8'))
|
| 29 |
+
dim_match = re.match(pattern, temp_str)
|
| 30 |
+
if dim_match:
|
| 31 |
+
width, height = map(int, dim_match.groups())
|
| 32 |
+
else:
|
| 33 |
+
temp_str += str(bytes.decode(file.readline(), encoding='utf-8'))
|
| 34 |
+
dim_match = re.match(pattern, temp_str)
|
| 35 |
+
if dim_match:
|
| 36 |
+
width, height = map(int, dim_match.groups())
|
| 37 |
+
else:
|
| 38 |
+
raise Exception('Malformed PFM header: width, height cannot be found')
|
| 39 |
+
|
| 40 |
+
scale = float(file.readline().rstrip())
|
| 41 |
+
if scale < 0: # little-endian
|
| 42 |
+
endian = '<'
|
| 43 |
+
scale = -scale
|
| 44 |
+
else:
|
| 45 |
+
endian = '>' # big-endian
|
| 46 |
+
|
| 47 |
+
data = np.fromfile(file, endian + 'f')
|
| 48 |
+
shape = (height, width, 3) if color else (height, width)
|
| 49 |
+
|
| 50 |
+
data = np.reshape(data, shape)
|
| 51 |
+
# DEY: I don't know why this was there.
|
| 52 |
+
file.close()
|
| 53 |
+
|
| 54 |
+
return data, scale
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def write_pfm(file, image, scale=1):
|
| 58 |
+
""" Write a pfm file """
|
| 59 |
+
file = open(file, 'wb')
|
| 60 |
+
|
| 61 |
+
color = None
|
| 62 |
+
|
| 63 |
+
if image.dtype.name != 'float32':
|
| 64 |
+
raise Exception('Image dtype must be float32.')
|
| 65 |
+
|
| 66 |
+
if len(image.shape) == 3 and image.shape[2] == 3: # color image
|
| 67 |
+
color = True
|
| 68 |
+
elif len(image.shape) == 2 or len(image.shape) == 3 and image.shape[2] == 1: # greyscale
|
| 69 |
+
color = False
|
| 70 |
+
else:
|
| 71 |
+
raise Exception('Image must have H x W x 3, H x W x 1 or H x W dimensions.')
|
| 72 |
+
|
| 73 |
+
file.write(bytes('PF\n', 'UTF-8') if color else bytes('Pf\n', 'UTF-8'))
|
| 74 |
+
temp_str = '%d %d\n' % (image.shape[1], image.shape[0])
|
| 75 |
+
file.write(bytes(temp_str, 'UTF-8'))
|
| 76 |
+
|
| 77 |
+
endian = image.dtype.byteorder
|
| 78 |
+
|
| 79 |
+
if endian == '<' or endian == '=' and sys.byteorder == 'little':
|
| 80 |
+
scale = -scale
|
| 81 |
+
|
| 82 |
+
temp_str = '%f\n' % scale
|
| 83 |
+
file.write(bytes(temp_str, 'UTF-8'))
|
| 84 |
+
|
| 85 |
+
image.tofile(file)
|
airsim/types.py
ADDED
|
@@ -0,0 +1,580 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import print_function
|
| 2 |
+
import msgpackrpc #install as admin: pip install msgpack-rpc-python
|
| 3 |
+
import numpy as np #pip install numpy
|
| 4 |
+
import math
|
| 5 |
+
|
| 6 |
+
class MsgpackMixin:
|
| 7 |
+
def __repr__(self):
|
| 8 |
+
from pprint import pformat
|
| 9 |
+
return "<" + type(self).__name__ + "> " + pformat(vars(self), indent=4, width=1)
|
| 10 |
+
|
| 11 |
+
def to_msgpack(self, *args, **kwargs):
|
| 12 |
+
return self.__dict__
|
| 13 |
+
|
| 14 |
+
@classmethod
|
| 15 |
+
def from_msgpack(cls, encoded):
|
| 16 |
+
obj = cls()
|
| 17 |
+
#obj.__dict__ = {k.decode('utf-8'): (from_msgpack(v.__class__, v) if hasattr(v, "__dict__") else v) for k, v in encoded.items()}
|
| 18 |
+
obj.__dict__ = { k : (v if not isinstance(v, dict) else getattr(getattr(obj, k).__class__, "from_msgpack")(v)) for k, v in encoded.items()}
|
| 19 |
+
#return cls(**msgpack.unpack(encoded))
|
| 20 |
+
return obj
|
| 21 |
+
|
| 22 |
+
class _ImageType(type):
|
| 23 |
+
@property
|
| 24 |
+
def Scene(cls):
|
| 25 |
+
return 0
|
| 26 |
+
def DepthPlanar(cls):
|
| 27 |
+
return 1
|
| 28 |
+
def DepthPerspective(cls):
|
| 29 |
+
return 2
|
| 30 |
+
def DepthVis(cls):
|
| 31 |
+
return 3
|
| 32 |
+
def DisparityNormalized(cls):
|
| 33 |
+
return 4
|
| 34 |
+
def Segmentation(cls):
|
| 35 |
+
return 5
|
| 36 |
+
def SurfaceNormals(cls):
|
| 37 |
+
return 6
|
| 38 |
+
def Infrared(cls):
|
| 39 |
+
return 7
|
| 40 |
+
def OpticalFlow(cls):
|
| 41 |
+
return 8
|
| 42 |
+
def OpticalFlowVis(cls):
|
| 43 |
+
return 9
|
| 44 |
+
|
| 45 |
+
def __getattr__(self, key):
|
| 46 |
+
if key == 'DepthPlanner':
|
| 47 |
+
print('\033[31m'+"DepthPlanner has been (correctly) renamed to DepthPlanar. Please use ImageType.DepthPlanar instead."+'\033[0m')
|
| 48 |
+
raise AttributeError
|
| 49 |
+
|
| 50 |
+
class ImageType(metaclass=_ImageType):
|
| 51 |
+
Scene = 0
|
| 52 |
+
DepthPlanar = 1
|
| 53 |
+
DepthPerspective = 2
|
| 54 |
+
DepthVis = 3
|
| 55 |
+
DisparityNormalized = 4
|
| 56 |
+
Segmentation = 5
|
| 57 |
+
SurfaceNormals = 6
|
| 58 |
+
Infrared = 7
|
| 59 |
+
OpticalFlow = 8
|
| 60 |
+
OpticalFlowVis = 9
|
| 61 |
+
|
| 62 |
+
class DrivetrainType:
|
| 63 |
+
MaxDegreeOfFreedom = 0
|
| 64 |
+
ForwardOnly = 1
|
| 65 |
+
|
| 66 |
+
class LandedState:
|
| 67 |
+
Landed = 0
|
| 68 |
+
Flying = 1
|
| 69 |
+
|
| 70 |
+
class WeatherParameter:
|
| 71 |
+
Rain = 0
|
| 72 |
+
Roadwetness = 1
|
| 73 |
+
Snow = 2
|
| 74 |
+
RoadSnow = 3
|
| 75 |
+
MapleLeaf = 4
|
| 76 |
+
RoadLeaf = 5
|
| 77 |
+
Dust = 6
|
| 78 |
+
Fog = 7
|
| 79 |
+
Enabled = 8
|
| 80 |
+
|
| 81 |
+
class Vector2r(MsgpackMixin):
|
| 82 |
+
x_val = 0.0
|
| 83 |
+
y_val = 0.0
|
| 84 |
+
|
| 85 |
+
def __init__(self, x_val = 0.0, y_val = 0.0):
|
| 86 |
+
self.x_val = x_val
|
| 87 |
+
self.y_val = y_val
|
| 88 |
+
|
| 89 |
+
class Vector3r(MsgpackMixin):
|
| 90 |
+
x_val = 0.0
|
| 91 |
+
y_val = 0.0
|
| 92 |
+
z_val = 0.0
|
| 93 |
+
|
| 94 |
+
def __init__(self, x_val = 0.0, y_val = 0.0, z_val = 0.0):
|
| 95 |
+
self.x_val = x_val
|
| 96 |
+
self.y_val = y_val
|
| 97 |
+
self.z_val = z_val
|
| 98 |
+
|
| 99 |
+
@staticmethod
|
| 100 |
+
def nanVector3r():
|
| 101 |
+
return Vector3r(np.nan, np.nan, np.nan)
|
| 102 |
+
|
| 103 |
+
def containsNan(self):
|
| 104 |
+
return (math.isnan(self.x_val) or math.isnan(self.y_val) or math.isnan(self.z_val))
|
| 105 |
+
|
| 106 |
+
def __add__(self, other):
|
| 107 |
+
return Vector3r(self.x_val + other.x_val, self.y_val + other.y_val, self.z_val + other.z_val)
|
| 108 |
+
|
| 109 |
+
def __sub__(self, other):
|
| 110 |
+
return Vector3r(self.x_val - other.x_val, self.y_val - other.y_val, self.z_val - other.z_val)
|
| 111 |
+
|
| 112 |
+
def __truediv__(self, other):
|
| 113 |
+
if type(other) in [int, float] + np.sctypes['int'] + np.sctypes['uint'] + np.sctypes['float']:
|
| 114 |
+
return Vector3r( self.x_val / other, self.y_val / other, self.z_val / other)
|
| 115 |
+
else:
|
| 116 |
+
raise TypeError('unsupported operand type(s) for /: %s and %s' % ( str(type(self)), str(type(other))) )
|
| 117 |
+
|
| 118 |
+
def __mul__(self, other):
|
| 119 |
+
if type(other) in [int, float] + np.sctypes['int'] + np.sctypes['uint'] + np.sctypes['float']:
|
| 120 |
+
return Vector3r(self.x_val*other, self.y_val*other, self.z_val*other)
|
| 121 |
+
else:
|
| 122 |
+
raise TypeError('unsupported operand type(s) for *: %s and %s' % ( str(type(self)), str(type(other))) )
|
| 123 |
+
|
| 124 |
+
def dot(self, other):
|
| 125 |
+
if type(self) == type(other):
|
| 126 |
+
return self.x_val*other.x_val + self.y_val*other.y_val + self.z_val*other.z_val
|
| 127 |
+
else:
|
| 128 |
+
raise TypeError('unsupported operand type(s) for \'dot\': %s and %s' % ( str(type(self)), str(type(other))) )
|
| 129 |
+
|
| 130 |
+
def cross(self, other):
|
| 131 |
+
if type(self) == type(other):
|
| 132 |
+
cross_product = np.cross(self.to_numpy_array(), other.to_numpy_array())
|
| 133 |
+
return Vector3r(cross_product[0], cross_product[1], cross_product[2])
|
| 134 |
+
else:
|
| 135 |
+
raise TypeError('unsupported operand type(s) for \'cross\': %s and %s' % ( str(type(self)), str(type(other))) )
|
| 136 |
+
|
| 137 |
+
def get_length(self):
|
| 138 |
+
return ( self.x_val**2 + self.y_val**2 + self.z_val**2 )**0.5
|
| 139 |
+
|
| 140 |
+
def distance_to(self, other):
|
| 141 |
+
return ( (self.x_val-other.x_val)**2 + (self.y_val-other.y_val)**2 + (self.z_val-other.z_val)**2 )**0.5
|
| 142 |
+
|
| 143 |
+
def to_Quaternionr(self):
|
| 144 |
+
return Quaternionr(self.x_val, self.y_val, self.z_val, 0)
|
| 145 |
+
|
| 146 |
+
def to_numpy_array(self):
|
| 147 |
+
return np.array([self.x_val, self.y_val, self.z_val], dtype=np.float32)
|
| 148 |
+
|
| 149 |
+
def __iter__(self):
|
| 150 |
+
return iter((self.x_val, self.y_val, self.z_val))
|
| 151 |
+
|
| 152 |
+
class Quaternionr(MsgpackMixin):
|
| 153 |
+
w_val = 0.0
|
| 154 |
+
x_val = 0.0
|
| 155 |
+
y_val = 0.0
|
| 156 |
+
z_val = 0.0
|
| 157 |
+
|
| 158 |
+
def __init__(self, x_val = 0.0, y_val = 0.0, z_val = 0.0, w_val = 1.0):
|
| 159 |
+
self.x_val = x_val
|
| 160 |
+
self.y_val = y_val
|
| 161 |
+
self.z_val = z_val
|
| 162 |
+
self.w_val = w_val
|
| 163 |
+
|
| 164 |
+
@staticmethod
|
| 165 |
+
def nanQuaternionr():
|
| 166 |
+
return Quaternionr(np.nan, np.nan, np.nan, np.nan)
|
| 167 |
+
|
| 168 |
+
def containsNan(self):
|
| 169 |
+
return (math.isnan(self.w_val) or math.isnan(self.x_val) or math.isnan(self.y_val) or math.isnan(self.z_val))
|
| 170 |
+
|
| 171 |
+
def __add__(self, other):
|
| 172 |
+
if type(self) == type(other):
|
| 173 |
+
return Quaternionr( self.x_val+other.x_val, self.y_val+other.y_val, self.z_val+other.z_val, self.w_val+other.w_val )
|
| 174 |
+
else:
|
| 175 |
+
raise TypeError('unsupported operand type(s) for +: %s and %s' % ( str(type(self)), str(type(other))) )
|
| 176 |
+
|
| 177 |
+
def __mul__(self, other):
|
| 178 |
+
if type(self) == type(other):
|
| 179 |
+
t, x, y, z = self.w_val, self.x_val, self.y_val, self.z_val
|
| 180 |
+
a, b, c, d = other.w_val, other.x_val, other.y_val, other.z_val
|
| 181 |
+
return Quaternionr( w_val = a*t - b*x - c*y - d*z,
|
| 182 |
+
x_val = b*t + a*x + d*y - c*z,
|
| 183 |
+
y_val = c*t + a*y + b*z - d*x,
|
| 184 |
+
z_val = d*t + z*a + c*x - b*y)
|
| 185 |
+
else:
|
| 186 |
+
raise TypeError('unsupported operand type(s) for *: %s and %s' % ( str(type(self)), str(type(other))) )
|
| 187 |
+
|
| 188 |
+
def __truediv__(self, other):
|
| 189 |
+
if type(other) == type(self):
|
| 190 |
+
return self * other.inverse()
|
| 191 |
+
elif type(other) in [int, float] + np.sctypes['int'] + np.sctypes['uint'] + np.sctypes['float']:
|
| 192 |
+
return Quaternionr( self.x_val / other, self.y_val / other, self.z_val / other, self.w_val / other)
|
| 193 |
+
else:
|
| 194 |
+
raise TypeError('unsupported operand type(s) for /: %s and %s' % ( str(type(self)), str(type(other))) )
|
| 195 |
+
|
| 196 |
+
def dot(self, other):
|
| 197 |
+
if type(self) == type(other):
|
| 198 |
+
return self.x_val*other.x_val + self.y_val*other.y_val + self.z_val*other.z_val + self.w_val*other.w_val
|
| 199 |
+
else:
|
| 200 |
+
raise TypeError('unsupported operand type(s) for \'dot\': %s and %s' % ( str(type(self)), str(type(other))) )
|
| 201 |
+
|
| 202 |
+
def cross(self, other):
|
| 203 |
+
if type(self) == type(other):
|
| 204 |
+
return (self * other - other * self) / 2
|
| 205 |
+
else:
|
| 206 |
+
raise TypeError('unsupported operand type(s) for \'cross\': %s and %s' % ( str(type(self)), str(type(other))) )
|
| 207 |
+
|
| 208 |
+
def outer_product(self, other):
|
| 209 |
+
if type(self) == type(other):
|
| 210 |
+
return ( self.inverse()*other - other.inverse()*self ) / 2
|
| 211 |
+
else:
|
| 212 |
+
raise TypeError('unsupported operand type(s) for \'outer_product\': %s and %s' % ( str(type(self)), str(type(other))) )
|
| 213 |
+
|
| 214 |
+
def rotate(self, other):
|
| 215 |
+
if type(self) == type(other):
|
| 216 |
+
if other.get_length() == 1:
|
| 217 |
+
return other * self * other.inverse()
|
| 218 |
+
else:
|
| 219 |
+
raise ValueError('length of the other Quaternionr must be 1')
|
| 220 |
+
else:
|
| 221 |
+
raise TypeError('unsupported operand type(s) for \'rotate\': %s and %s' % ( str(type(self)), str(type(other))) )
|
| 222 |
+
|
| 223 |
+
def conjugate(self):
|
| 224 |
+
return Quaternionr(-self.x_val, -self.y_val, -self.z_val, self.w_val)
|
| 225 |
+
|
| 226 |
+
def star(self):
|
| 227 |
+
return self.conjugate()
|
| 228 |
+
|
| 229 |
+
def inverse(self):
|
| 230 |
+
return self.star() / self.dot(self)
|
| 231 |
+
|
| 232 |
+
def sgn(self):
|
| 233 |
+
return self/self.get_length()
|
| 234 |
+
|
| 235 |
+
def get_length(self):
|
| 236 |
+
return ( self.x_val**2 + self.y_val**2 + self.z_val**2 + self.w_val**2 )**0.5
|
| 237 |
+
|
| 238 |
+
def to_numpy_array(self):
|
| 239 |
+
return np.array([self.x_val, self.y_val, self.z_val, self.w_val], dtype=np.float32)
|
| 240 |
+
|
| 241 |
+
def __iter__(self):
|
| 242 |
+
return iter((self.x_val, self.y_val, self.z_val, self.w_val))
|
| 243 |
+
|
| 244 |
+
class Pose(MsgpackMixin):
|
| 245 |
+
position = Vector3r()
|
| 246 |
+
orientation = Quaternionr()
|
| 247 |
+
|
| 248 |
+
def __init__(self, position_val = None, orientation_val = None):
|
| 249 |
+
position_val = position_val if position_val is not None else Vector3r()
|
| 250 |
+
orientation_val = orientation_val if orientation_val is not None else Quaternionr()
|
| 251 |
+
self.position = position_val
|
| 252 |
+
self.orientation = orientation_val
|
| 253 |
+
|
| 254 |
+
@staticmethod
|
| 255 |
+
def nanPose():
|
| 256 |
+
return Pose(Vector3r.nanVector3r(), Quaternionr.nanQuaternionr())
|
| 257 |
+
|
| 258 |
+
def containsNan(self):
|
| 259 |
+
return (self.position.containsNan() or self.orientation.containsNan())
|
| 260 |
+
|
| 261 |
+
def __iter__(self):
|
| 262 |
+
return iter((self.position, self.orientation))
|
| 263 |
+
|
| 264 |
+
class CollisionInfo(MsgpackMixin):
|
| 265 |
+
has_collided = False
|
| 266 |
+
normal = Vector3r()
|
| 267 |
+
impact_point = Vector3r()
|
| 268 |
+
position = Vector3r()
|
| 269 |
+
penetration_depth = 0.0
|
| 270 |
+
time_stamp = 0.0
|
| 271 |
+
object_name = ""
|
| 272 |
+
object_id = -1
|
| 273 |
+
|
| 274 |
+
class GeoPoint(MsgpackMixin):
|
| 275 |
+
latitude = 0.0
|
| 276 |
+
longitude = 0.0
|
| 277 |
+
altitude = 0.0
|
| 278 |
+
|
| 279 |
+
class YawMode(MsgpackMixin):
|
| 280 |
+
is_rate = True
|
| 281 |
+
yaw_or_rate = 0.0
|
| 282 |
+
def __init__(self, is_rate = True, yaw_or_rate = 0.0):
|
| 283 |
+
self.is_rate = is_rate
|
| 284 |
+
self.yaw_or_rate = yaw_or_rate
|
| 285 |
+
|
| 286 |
+
class RCData(MsgpackMixin):
|
| 287 |
+
timestamp = 0
|
| 288 |
+
pitch, roll, throttle, yaw = (0.0,)*4 #init 4 variable to 0.0
|
| 289 |
+
switch1, switch2, switch3, switch4 = (0,)*4
|
| 290 |
+
switch5, switch6, switch7, switch8 = (0,)*4
|
| 291 |
+
is_initialized = False
|
| 292 |
+
is_valid = False
|
| 293 |
+
def __init__(self, timestamp = 0, pitch = 0.0, roll = 0.0, throttle = 0.0, yaw = 0.0, switch1 = 0,
|
| 294 |
+
switch2 = 0, switch3 = 0, switch4 = 0, switch5 = 0, switch6 = 0, switch7 = 0, switch8 = 0, is_initialized = False, is_valid = False):
|
| 295 |
+
self.timestamp = timestamp
|
| 296 |
+
self.pitch = pitch
|
| 297 |
+
self.roll = roll
|
| 298 |
+
self.throttle = throttle
|
| 299 |
+
self.yaw = yaw
|
| 300 |
+
self.switch1 = switch1
|
| 301 |
+
self.switch2 = switch2
|
| 302 |
+
self.switch3 = switch3
|
| 303 |
+
self.switch4 = switch4
|
| 304 |
+
self.switch5 = switch5
|
| 305 |
+
self.switch6 = switch6
|
| 306 |
+
self.switch7 = switch7
|
| 307 |
+
self.switch8 = switch8
|
| 308 |
+
self.is_initialized = is_initialized
|
| 309 |
+
self.is_valid = is_valid
|
| 310 |
+
|
| 311 |
+
class ImageRequest(MsgpackMixin):
|
| 312 |
+
camera_name = '0'
|
| 313 |
+
image_type = ImageType.Scene
|
| 314 |
+
pixels_as_float = False
|
| 315 |
+
compress = False
|
| 316 |
+
|
| 317 |
+
def __init__(self, camera_name, image_type, pixels_as_float = False, compress = True):
|
| 318 |
+
# todo: in future remove str(), it's only for compatibility to pre v1.2
|
| 319 |
+
self.camera_name = str(camera_name)
|
| 320 |
+
self.image_type = image_type
|
| 321 |
+
self.pixels_as_float = pixels_as_float
|
| 322 |
+
self.compress = compress
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
class ImageResponse(MsgpackMixin):
|
| 326 |
+
image_data_uint8 = np.uint8(0)
|
| 327 |
+
image_data_float = 0.0
|
| 328 |
+
camera_position = Vector3r()
|
| 329 |
+
camera_orientation = Quaternionr()
|
| 330 |
+
time_stamp = np.uint64(0)
|
| 331 |
+
message = ''
|
| 332 |
+
pixels_as_float = 0.0
|
| 333 |
+
compress = True
|
| 334 |
+
width = 0
|
| 335 |
+
height = 0
|
| 336 |
+
image_type = ImageType.Scene
|
| 337 |
+
|
| 338 |
+
class CarControls(MsgpackMixin):
|
| 339 |
+
throttle = 0.0
|
| 340 |
+
steering = 0.0
|
| 341 |
+
brake = 0.0
|
| 342 |
+
handbrake = False
|
| 343 |
+
is_manual_gear = False
|
| 344 |
+
manual_gear = 0
|
| 345 |
+
gear_immediate = True
|
| 346 |
+
|
| 347 |
+
def __init__(self, throttle = 0, steering = 0, brake = 0,
|
| 348 |
+
handbrake = False, is_manual_gear = False, manual_gear = 0, gear_immediate = True):
|
| 349 |
+
self.throttle = throttle
|
| 350 |
+
self.steering = steering
|
| 351 |
+
self.brake = brake
|
| 352 |
+
self.handbrake = handbrake
|
| 353 |
+
self.is_manual_gear = is_manual_gear
|
| 354 |
+
self.manual_gear = manual_gear
|
| 355 |
+
self.gear_immediate = gear_immediate
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
def set_throttle(self, throttle_val, forward):
|
| 359 |
+
if (forward):
|
| 360 |
+
self.is_manual_gear = False
|
| 361 |
+
self.manual_gear = 0
|
| 362 |
+
self.throttle = abs(throttle_val)
|
| 363 |
+
else:
|
| 364 |
+
self.is_manual_gear = False
|
| 365 |
+
self.manual_gear = -1
|
| 366 |
+
self.throttle = - abs(throttle_val)
|
| 367 |
+
|
| 368 |
+
class KinematicsState(MsgpackMixin):
|
| 369 |
+
position = Vector3r()
|
| 370 |
+
orientation = Quaternionr()
|
| 371 |
+
linear_velocity = Vector3r()
|
| 372 |
+
angular_velocity = Vector3r()
|
| 373 |
+
linear_acceleration = Vector3r()
|
| 374 |
+
angular_acceleration = Vector3r()
|
| 375 |
+
|
| 376 |
+
class EnvironmentState(MsgpackMixin):
|
| 377 |
+
position = Vector3r()
|
| 378 |
+
geo_point = GeoPoint()
|
| 379 |
+
gravity = Vector3r()
|
| 380 |
+
air_pressure = 0.0
|
| 381 |
+
temperature = 0.0
|
| 382 |
+
air_density = 0.0
|
| 383 |
+
|
| 384 |
+
class CarState(MsgpackMixin):
|
| 385 |
+
speed = 0.0
|
| 386 |
+
gear = 0
|
| 387 |
+
rpm = 0.0
|
| 388 |
+
maxrpm = 0.0
|
| 389 |
+
handbrake = False
|
| 390 |
+
collision = CollisionInfo()
|
| 391 |
+
kinematics_estimated = KinematicsState()
|
| 392 |
+
timestamp = np.uint64(0)
|
| 393 |
+
|
| 394 |
+
class MultirotorState(MsgpackMixin):
|
| 395 |
+
collision = CollisionInfo()
|
| 396 |
+
kinematics_estimated = KinematicsState()
|
| 397 |
+
gps_location = GeoPoint()
|
| 398 |
+
timestamp = np.uint64(0)
|
| 399 |
+
landed_state = LandedState.Landed
|
| 400 |
+
rc_data = RCData()
|
| 401 |
+
ready = False
|
| 402 |
+
ready_message = ""
|
| 403 |
+
can_arm = False
|
| 404 |
+
|
| 405 |
+
class RotorStates(MsgpackMixin):
|
| 406 |
+
timestamp = np.uint64(0)
|
| 407 |
+
rotors = []
|
| 408 |
+
|
| 409 |
+
class ProjectionMatrix(MsgpackMixin):
|
| 410 |
+
matrix = []
|
| 411 |
+
|
| 412 |
+
class CameraInfo(MsgpackMixin):
|
| 413 |
+
pose = Pose()
|
| 414 |
+
fov = -1
|
| 415 |
+
proj_mat = ProjectionMatrix()
|
| 416 |
+
|
| 417 |
+
class LidarData(MsgpackMixin):
|
| 418 |
+
point_cloud = 0.0
|
| 419 |
+
time_stamp = np.uint64(0)
|
| 420 |
+
pose = Pose()
|
| 421 |
+
segmentation = 0
|
| 422 |
+
|
| 423 |
+
class ImuData(MsgpackMixin):
|
| 424 |
+
time_stamp = np.uint64(0)
|
| 425 |
+
orientation = Quaternionr()
|
| 426 |
+
angular_velocity = Vector3r()
|
| 427 |
+
linear_acceleration = Vector3r()
|
| 428 |
+
|
| 429 |
+
class BarometerData(MsgpackMixin):
|
| 430 |
+
time_stamp = np.uint64(0)
|
| 431 |
+
altitude = Quaternionr()
|
| 432 |
+
pressure = Vector3r()
|
| 433 |
+
qnh = Vector3r()
|
| 434 |
+
|
| 435 |
+
class MagnetometerData(MsgpackMixin):
|
| 436 |
+
time_stamp = np.uint64(0)
|
| 437 |
+
magnetic_field_body = Vector3r()
|
| 438 |
+
magnetic_field_covariance = 0.0
|
| 439 |
+
|
| 440 |
+
class GnssFixType(MsgpackMixin):
|
| 441 |
+
GNSS_FIX_NO_FIX = 0
|
| 442 |
+
GNSS_FIX_TIME_ONLY = 1
|
| 443 |
+
GNSS_FIX_2D_FIX = 2
|
| 444 |
+
GNSS_FIX_3D_FIX = 3
|
| 445 |
+
|
| 446 |
+
class GnssReport(MsgpackMixin):
|
| 447 |
+
geo_point = GeoPoint()
|
| 448 |
+
eph = 0.0
|
| 449 |
+
epv = 0.0
|
| 450 |
+
velocity = Vector3r()
|
| 451 |
+
fix_type = GnssFixType()
|
| 452 |
+
time_utc = np.uint64(0)
|
| 453 |
+
|
| 454 |
+
class GpsData(MsgpackMixin):
|
| 455 |
+
time_stamp = np.uint64(0)
|
| 456 |
+
gnss = GnssReport()
|
| 457 |
+
is_valid = False
|
| 458 |
+
|
| 459 |
+
class DistanceSensorData(MsgpackMixin):
|
| 460 |
+
time_stamp = np.uint64(0)
|
| 461 |
+
distance = 0.0
|
| 462 |
+
min_distance = 0.0
|
| 463 |
+
max_distance = 0.0
|
| 464 |
+
relative_pose = Pose()
|
| 465 |
+
|
| 466 |
+
class Box2D(MsgpackMixin):
|
| 467 |
+
min = Vector2r()
|
| 468 |
+
max = Vector2r()
|
| 469 |
+
|
| 470 |
+
class Box3D(MsgpackMixin):
|
| 471 |
+
min = Vector3r()
|
| 472 |
+
max = Vector3r()
|
| 473 |
+
|
| 474 |
+
class DetectionInfo(MsgpackMixin):
|
| 475 |
+
name = ''
|
| 476 |
+
geo_point = GeoPoint()
|
| 477 |
+
box2D = Box2D()
|
| 478 |
+
box3D = Box3D()
|
| 479 |
+
relative_pose = Pose()
|
| 480 |
+
|
| 481 |
+
class PIDGains():
|
| 482 |
+
"""
|
| 483 |
+
Struct to store values of PID gains. Used to transmit controller gain values while instantiating
|
| 484 |
+
AngleLevel/AngleRate/Velocity/PositionControllerGains objects.
|
| 485 |
+
|
| 486 |
+
Attributes:
|
| 487 |
+
kP (float): Proportional gain
|
| 488 |
+
kI (float): Integrator gain
|
| 489 |
+
kD (float): Derivative gain
|
| 490 |
+
"""
|
| 491 |
+
def __init__(self, kp, ki, kd):
|
| 492 |
+
self.kp = kp
|
| 493 |
+
self.ki = ki
|
| 494 |
+
self.kd = kd
|
| 495 |
+
|
| 496 |
+
def to_list(self):
|
| 497 |
+
return [self.kp, self.ki, self.kd]
|
| 498 |
+
|
| 499 |
+
class AngleRateControllerGains():
|
| 500 |
+
"""
|
| 501 |
+
Struct to contain controller gains used by angle level PID controller
|
| 502 |
+
|
| 503 |
+
Attributes:
|
| 504 |
+
roll_gains (PIDGains): kP, kI, kD for roll axis
|
| 505 |
+
pitch_gains (PIDGains): kP, kI, kD for pitch axis
|
| 506 |
+
yaw_gains (PIDGains): kP, kI, kD for yaw axis
|
| 507 |
+
"""
|
| 508 |
+
def __init__(self, roll_gains = PIDGains(0.25, 0, 0),
|
| 509 |
+
pitch_gains = PIDGains(0.25, 0, 0),
|
| 510 |
+
yaw_gains = PIDGains(0.25, 0, 0)):
|
| 511 |
+
self.roll_gains = roll_gains
|
| 512 |
+
self.pitch_gains = pitch_gains
|
| 513 |
+
self.yaw_gains = yaw_gains
|
| 514 |
+
|
| 515 |
+
def to_lists(self):
|
| 516 |
+
return [self.roll_gains.kp, self.pitch_gains.kp, self.yaw_gains.kp], [self.roll_gains.ki, self.pitch_gains.ki, self.yaw_gains.ki], [self.roll_gains.kd, self.pitch_gains.kd, self.yaw_gains.kd]
|
| 517 |
+
|
| 518 |
+
class AngleLevelControllerGains():
|
| 519 |
+
"""
|
| 520 |
+
Struct to contain controller gains used by angle rate PID controller
|
| 521 |
+
|
| 522 |
+
Attributes:
|
| 523 |
+
roll_gains (PIDGains): kP, kI, kD for roll axis
|
| 524 |
+
pitch_gains (PIDGains): kP, kI, kD for pitch axis
|
| 525 |
+
yaw_gains (PIDGains): kP, kI, kD for yaw axis
|
| 526 |
+
"""
|
| 527 |
+
def __init__(self, roll_gains = PIDGains(2.5, 0, 0),
|
| 528 |
+
pitch_gains = PIDGains(2.5, 0, 0),
|
| 529 |
+
yaw_gains = PIDGains(2.5, 0, 0)):
|
| 530 |
+
self.roll_gains = roll_gains
|
| 531 |
+
self.pitch_gains = pitch_gains
|
| 532 |
+
self.yaw_gains = yaw_gains
|
| 533 |
+
|
| 534 |
+
def to_lists(self):
|
| 535 |
+
return [self.roll_gains.kp, self.pitch_gains.kp, self.yaw_gains.kp], [self.roll_gains.ki, self.pitch_gains.ki, self.yaw_gains.ki], [self.roll_gains.kd, self.pitch_gains.kd, self.yaw_gains.kd]
|
| 536 |
+
|
| 537 |
+
class VelocityControllerGains():
|
| 538 |
+
"""
|
| 539 |
+
Struct to contain controller gains used by velocity PID controller
|
| 540 |
+
|
| 541 |
+
Attributes:
|
| 542 |
+
x_gains (PIDGains): kP, kI, kD for X axis
|
| 543 |
+
y_gains (PIDGains): kP, kI, kD for Y axis
|
| 544 |
+
z_gains (PIDGains): kP, kI, kD for Z axis
|
| 545 |
+
"""
|
| 546 |
+
def __init__(self, x_gains = PIDGains(0.2, 0, 0),
|
| 547 |
+
y_gains = PIDGains(0.2, 0, 0),
|
| 548 |
+
z_gains = PIDGains(2.0, 2.0, 0)):
|
| 549 |
+
self.x_gains = x_gains
|
| 550 |
+
self.y_gains = y_gains
|
| 551 |
+
self.z_gains = z_gains
|
| 552 |
+
|
| 553 |
+
def to_lists(self):
|
| 554 |
+
return [self.x_gains.kp, self.y_gains.kp, self.z_gains.kp], [self.x_gains.ki, self.y_gains.ki, self.z_gains.ki], [self.x_gains.kd, self.y_gains.kd, self.z_gains.kd]
|
| 555 |
+
|
| 556 |
+
class PositionControllerGains():
|
| 557 |
+
"""
|
| 558 |
+
Struct to contain controller gains used by position PID controller
|
| 559 |
+
|
| 560 |
+
Attributes:
|
| 561 |
+
x_gains (PIDGains): kP, kI, kD for X axis
|
| 562 |
+
y_gains (PIDGains): kP, kI, kD for Y axis
|
| 563 |
+
z_gains (PIDGains): kP, kI, kD for Z axis
|
| 564 |
+
"""
|
| 565 |
+
def __init__(self, x_gains = PIDGains(0.25, 0, 0),
|
| 566 |
+
y_gains = PIDGains(0.25, 0, 0),
|
| 567 |
+
z_gains = PIDGains(0.25, 0, 0)):
|
| 568 |
+
self.x_gains = x_gains
|
| 569 |
+
self.y_gains = y_gains
|
| 570 |
+
self.z_gains = z_gains
|
| 571 |
+
|
| 572 |
+
def to_lists(self):
|
| 573 |
+
return [self.x_gains.kp, self.y_gains.kp, self.z_gains.kp], [self.x_gains.ki, self.y_gains.ki, self.z_gains.ki], [self.x_gains.kd, self.y_gains.kd, self.z_gains.kd]
|
| 574 |
+
|
| 575 |
+
class MeshPositionVertexBuffersResponse(MsgpackMixin):
|
| 576 |
+
position = Vector3r()
|
| 577 |
+
orientation = Quaternionr()
|
| 578 |
+
vertices = 0.0
|
| 579 |
+
indices = 0.0
|
| 580 |
+
name = ''
|
airsim/utils.py
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np #pip install numpy
|
| 2 |
+
import math
|
| 3 |
+
import time
|
| 4 |
+
import sys
|
| 5 |
+
import os
|
| 6 |
+
import inspect
|
| 7 |
+
import types
|
| 8 |
+
import re
|
| 9 |
+
import logging
|
| 10 |
+
|
| 11 |
+
from .types import *
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def string_to_uint8_array(bstr):
|
| 15 |
+
return np.fromstring(bstr, np.uint8)
|
| 16 |
+
|
| 17 |
+
def string_to_float_array(bstr):
|
| 18 |
+
return np.fromstring(bstr, np.float32)
|
| 19 |
+
|
| 20 |
+
def list_to_2d_float_array(flst, width, height):
|
| 21 |
+
return np.reshape(np.asarray(flst, np.float32), (height, width))
|
| 22 |
+
|
| 23 |
+
def get_pfm_array(response):
|
| 24 |
+
return list_to_2d_float_array(response.image_data_float, response.width, response.height)
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def get_public_fields(obj):
|
| 28 |
+
return [attr for attr in dir(obj)
|
| 29 |
+
if not (attr.startswith("_")
|
| 30 |
+
or inspect.isbuiltin(attr)
|
| 31 |
+
or inspect.isfunction(attr)
|
| 32 |
+
or inspect.ismethod(attr))]
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def to_dict(obj):
|
| 37 |
+
return dict([attr, getattr(obj, attr)] for attr in get_public_fields(obj))
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def to_str(obj):
|
| 41 |
+
return str(to_dict(obj))
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def write_file(filename, bstr):
|
| 45 |
+
"""
|
| 46 |
+
Write binary data to file.
|
| 47 |
+
Used for writing compressed PNG images
|
| 48 |
+
"""
|
| 49 |
+
with open(filename, 'wb') as afile:
|
| 50 |
+
afile.write(bstr)
|
| 51 |
+
|
| 52 |
+
# helper method for converting getOrientation to roll/pitch/yaw
|
| 53 |
+
# https:#en.wikipedia.org/wiki/Conversion_between_quaternions_and_Euler_angles
|
| 54 |
+
|
| 55 |
+
def to_eularian_angles(q):
|
| 56 |
+
z = q.z_val
|
| 57 |
+
y = q.y_val
|
| 58 |
+
x = q.x_val
|
| 59 |
+
w = q.w_val
|
| 60 |
+
ysqr = y * y
|
| 61 |
+
|
| 62 |
+
# roll (x-axis rotation)
|
| 63 |
+
t0 = +2.0 * (w*x + y*z)
|
| 64 |
+
t1 = +1.0 - 2.0*(x*x + ysqr)
|
| 65 |
+
roll = math.atan2(t0, t1)
|
| 66 |
+
|
| 67 |
+
# pitch (y-axis rotation)
|
| 68 |
+
t2 = +2.0 * (w*y - z*x)
|
| 69 |
+
if (t2 > 1.0):
|
| 70 |
+
t2 = 1
|
| 71 |
+
if (t2 < -1.0):
|
| 72 |
+
t2 = -1.0
|
| 73 |
+
pitch = math.asin(t2)
|
| 74 |
+
|
| 75 |
+
# yaw (z-axis rotation)
|
| 76 |
+
t3 = +2.0 * (w*z + x*y)
|
| 77 |
+
t4 = +1.0 - 2.0 * (ysqr + z*z)
|
| 78 |
+
yaw = math.atan2(t3, t4)
|
| 79 |
+
|
| 80 |
+
return (pitch, roll, yaw)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def to_quaternion(pitch, roll, yaw):
|
| 84 |
+
t0 = math.cos(yaw * 0.5)
|
| 85 |
+
t1 = math.sin(yaw * 0.5)
|
| 86 |
+
t2 = math.cos(roll * 0.5)
|
| 87 |
+
t3 = math.sin(roll * 0.5)
|
| 88 |
+
t4 = math.cos(pitch * 0.5)
|
| 89 |
+
t5 = math.sin(pitch * 0.5)
|
| 90 |
+
|
| 91 |
+
q = Quaternionr()
|
| 92 |
+
q.w_val = t0 * t2 * t4 + t1 * t3 * t5 #w
|
| 93 |
+
q.x_val = t0 * t3 * t4 - t1 * t2 * t5 #x
|
| 94 |
+
q.y_val = t0 * t2 * t5 + t1 * t3 * t4 #y
|
| 95 |
+
q.z_val = t1 * t2 * t4 - t0 * t3 * t5 #z
|
| 96 |
+
return q
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def wait_key(message = ''):
|
| 100 |
+
''' Wait for a key press on the console and return it. '''
|
| 101 |
+
if message != '':
|
| 102 |
+
print (message)
|
| 103 |
+
|
| 104 |
+
result = None
|
| 105 |
+
if os.name == 'nt':
|
| 106 |
+
import msvcrt
|
| 107 |
+
result = msvcrt.getch()
|
| 108 |
+
else:
|
| 109 |
+
import termios
|
| 110 |
+
fd = sys.stdin.fileno()
|
| 111 |
+
|
| 112 |
+
oldterm = termios.tcgetattr(fd)
|
| 113 |
+
newattr = termios.tcgetattr(fd)
|
| 114 |
+
newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO
|
| 115 |
+
termios.tcsetattr(fd, termios.TCSANOW, newattr)
|
| 116 |
+
|
| 117 |
+
try:
|
| 118 |
+
result = sys.stdin.read(1)
|
| 119 |
+
except IOError:
|
| 120 |
+
pass
|
| 121 |
+
finally:
|
| 122 |
+
termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)
|
| 123 |
+
|
| 124 |
+
return result
|
| 125 |
+
|
| 126 |
+
|
| 127 |
+
def read_pfm(file):
|
| 128 |
+
""" Read a pfm file """
|
| 129 |
+
file = open(file, 'rb')
|
| 130 |
+
|
| 131 |
+
color = None
|
| 132 |
+
width = None
|
| 133 |
+
height = None
|
| 134 |
+
scale = None
|
| 135 |
+
endian = None
|
| 136 |
+
|
| 137 |
+
header = file.readline().rstrip()
|
| 138 |
+
header = str(bytes.decode(header, encoding='utf-8'))
|
| 139 |
+
if header == 'PF':
|
| 140 |
+
color = True
|
| 141 |
+
elif header == 'Pf':
|
| 142 |
+
color = False
|
| 143 |
+
else:
|
| 144 |
+
raise Exception('Not a PFM file.')
|
| 145 |
+
|
| 146 |
+
temp_str = str(bytes.decode(file.readline(), encoding='utf-8'))
|
| 147 |
+
dim_match = re.match(r'^(\d+)\s(\d+)\s$', temp_str)
|
| 148 |
+
if dim_match:
|
| 149 |
+
width, height = map(int, dim_match.groups())
|
| 150 |
+
else:
|
| 151 |
+
raise Exception('Malformed PFM header.')
|
| 152 |
+
|
| 153 |
+
scale = float(file.readline().rstrip())
|
| 154 |
+
if scale < 0: # little-endian
|
| 155 |
+
endian = '<'
|
| 156 |
+
scale = -scale
|
| 157 |
+
else:
|
| 158 |
+
endian = '>' # big-endian
|
| 159 |
+
|
| 160 |
+
data = np.fromfile(file, endian + 'f')
|
| 161 |
+
shape = (height, width, 3) if color else (height, width)
|
| 162 |
+
|
| 163 |
+
data = np.reshape(data, shape)
|
| 164 |
+
# DEY: I don't know why this was there.
|
| 165 |
+
file.close()
|
| 166 |
+
|
| 167 |
+
return data, scale
|
| 168 |
+
|
| 169 |
+
|
| 170 |
+
def write_pfm(file, image, scale=1):
|
| 171 |
+
""" Write a pfm file """
|
| 172 |
+
file = open(file, 'wb')
|
| 173 |
+
|
| 174 |
+
color = None
|
| 175 |
+
|
| 176 |
+
if image.dtype.name != 'float32':
|
| 177 |
+
raise Exception('Image dtype must be float32.')
|
| 178 |
+
|
| 179 |
+
if len(image.shape) == 3 and image.shape[2] == 3: # color image
|
| 180 |
+
color = True
|
| 181 |
+
elif len(image.shape) == 2 or len(image.shape) == 3 and image.shape[2] == 1: # grayscale
|
| 182 |
+
color = False
|
| 183 |
+
else:
|
| 184 |
+
raise Exception('Image must have H x W x 3, H x W x 1 or H x W dimensions.')
|
| 185 |
+
|
| 186 |
+
file.write('PF\n'.encode('utf-8') if color else 'Pf\n'.encode('utf-8'))
|
| 187 |
+
temp_str = '%d %d\n' % (image.shape[1], image.shape[0])
|
| 188 |
+
file.write(temp_str.encode('utf-8'))
|
| 189 |
+
|
| 190 |
+
endian = image.dtype.byteorder
|
| 191 |
+
|
| 192 |
+
if endian == '<' or endian == '=' and sys.byteorder == 'little':
|
| 193 |
+
scale = -scale
|
| 194 |
+
|
| 195 |
+
temp_str = '%f\n' % scale
|
| 196 |
+
file.write(temp_str.encode('utf-8'))
|
| 197 |
+
|
| 198 |
+
image.tofile(file)
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
def write_png(filename, image):
|
| 202 |
+
""" image must be numpy array H X W X channels
|
| 203 |
+
"""
|
| 204 |
+
import cv2 # pip install opencv-python
|
| 205 |
+
|
| 206 |
+
ret = cv2.imwrite(filename, image)
|
| 207 |
+
if not ret:
|
| 208 |
+
logging.error(f"Writing PNG file {filename} failed")
|
create_ir_segmentation_map.py
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy
|
| 2 |
+
import cv2
|
| 3 |
+
import time
|
| 4 |
+
import sys
|
| 5 |
+
import os
|
| 6 |
+
import random
|
| 7 |
+
from airsim import *
|
| 8 |
+
|
| 9 |
+
def radiance(absoluteTemperature, emissivity, dx=0.01, response=None):
|
| 10 |
+
"""
|
| 11 |
+
title::
|
| 12 |
+
radiance
|
| 13 |
+
|
| 14 |
+
description::
|
| 15 |
+
Calculates radiance and integrated radiance over a bandpass of 8 to 14
|
| 16 |
+
microns, given temperature and emissivity, using Planck's Law.
|
| 17 |
+
|
| 18 |
+
inputs::
|
| 19 |
+
absoluteTemperature
|
| 20 |
+
temperture of object in [K]
|
| 21 |
+
|
| 22 |
+
either a single temperature or a numpy
|
| 23 |
+
array of temperatures, of shape (temperatures.shape[0], 1)
|
| 24 |
+
emissivity
|
| 25 |
+
average emissivity (number between 0 and 1 representing the
|
| 26 |
+
efficiency with which it emits radiation; if 1, it is an ideal
|
| 27 |
+
blackbody) of object over the bandpass
|
| 28 |
+
|
| 29 |
+
either a single emissivity or a numpy array of emissivities, of
|
| 30 |
+
shape (emissivities.shape[0], 1)
|
| 31 |
+
dx
|
| 32 |
+
discrete spacing between the wavelengths for evaluation of
|
| 33 |
+
radiance and integration [default is 0.1]
|
| 34 |
+
response
|
| 35 |
+
optional response of the camera over the bandpass of 8 to 14
|
| 36 |
+
microns [default is None, for no response provided]
|
| 37 |
+
|
| 38 |
+
returns::
|
| 39 |
+
radiance
|
| 40 |
+
discrete spectrum of radiance over bandpass
|
| 41 |
+
integratedRadiance
|
| 42 |
+
integration of radiance spectrum over bandpass (to simulate
|
| 43 |
+
the readout from a sensor)
|
| 44 |
+
|
| 45 |
+
author::
|
| 46 |
+
Elizabeth Bondi
|
| 47 |
+
"""
|
| 48 |
+
wavelength = numpy.arange(8,14,dx)
|
| 49 |
+
c1 = 1.19104e8 # (2 * 6.62607*10^-34 [Js] *
|
| 50 |
+
# (2.99792458 * 10^14 [micron/s])^2 * 10^12 to convert
|
| 51 |
+
# denominator from microns^3 to microns * m^2)
|
| 52 |
+
c2 = 1.43879e4 # (hc/k) [micron * K]
|
| 53 |
+
if response is not None:
|
| 54 |
+
radiance = response * emissivity * (c1 / ((wavelength**5) * \
|
| 55 |
+
(numpy.exp(c2 / (wavelength * absoluteTemperature )) - 1)))
|
| 56 |
+
else:
|
| 57 |
+
radiance = emissivity * (c1 / ((wavelength**5) * (numpy.exp(c2 / \
|
| 58 |
+
(wavelength * absoluteTemperature )) - 1)))
|
| 59 |
+
if absoluteTemperature.ndim > 1:
|
| 60 |
+
return radiance, numpy.trapz(radiance, dx=dx, axis=1)
|
| 61 |
+
else:
|
| 62 |
+
return radiance, numpy.trapz(radiance, dx=dx)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def get_new_temp_emiss_from_radiance(tempEmissivity, response):
|
| 66 |
+
"""
|
| 67 |
+
title::
|
| 68 |
+
get_new_temp_emiss_from_radiance
|
| 69 |
+
|
| 70 |
+
description::
|
| 71 |
+
Transform tempEmissivity from [objectName, temperature, emissivity]
|
| 72 |
+
to [objectName, "radiance"] using radiance calculation above.
|
| 73 |
+
|
| 74 |
+
input::
|
| 75 |
+
tempEmissivity
|
| 76 |
+
numpy array containing the temperature and emissivity of each
|
| 77 |
+
object (e.g., each row has: [objectName, temperature, emissivity])
|
| 78 |
+
response
|
| 79 |
+
camera response (same input as radiance, set to None if lacking
|
| 80 |
+
this information)
|
| 81 |
+
|
| 82 |
+
returns::
|
| 83 |
+
tempEmissivityNew
|
| 84 |
+
tempEmissivity, now with [objectName, "radiance"]; note that
|
| 85 |
+
integrated radiance (L) is divided by the maximum and multiplied
|
| 86 |
+
by 255 in order to simulate an 8 bit digital count observed by the
|
| 87 |
+
thermal sensor, since radiance and digital count are linearly
|
| 88 |
+
related, so it's [objectName, simulated thermal digital count]
|
| 89 |
+
|
| 90 |
+
author::
|
| 91 |
+
Elizabeth Bondi
|
| 92 |
+
"""
|
| 93 |
+
numObjects = tempEmissivity.shape[0]
|
| 94 |
+
|
| 95 |
+
L = radiance(tempEmissivity[:,1].reshape((-1,1)).astype(numpy.float64),
|
| 96 |
+
tempEmissivity[:,2].reshape((-1,1)).astype(numpy.float64),
|
| 97 |
+
response=response)[1].flatten()
|
| 98 |
+
L = ((L / L.max()) * 255).astype(numpy.uint8)
|
| 99 |
+
|
| 100 |
+
tempEmissivityNew = numpy.hstack((
|
| 101 |
+
tempEmissivity[:,0].reshape((numObjects,1)),
|
| 102 |
+
L.reshape((numObjects,1))))
|
| 103 |
+
|
| 104 |
+
return tempEmissivityNew
|
| 105 |
+
|
| 106 |
+
def set_segmentation_ids(segIdDict, tempEmissivityNew, client):
|
| 107 |
+
"""
|
| 108 |
+
title::
|
| 109 |
+
set_segmentation_ids
|
| 110 |
+
|
| 111 |
+
description::
|
| 112 |
+
Set stencil IDs in environment so that stencil IDs correspond to
|
| 113 |
+
simulated thermal digital counts (e.g., if elephant has a simulated
|
| 114 |
+
digital count of 219, set stencil ID to 219).
|
| 115 |
+
|
| 116 |
+
input::
|
| 117 |
+
segIdDict
|
| 118 |
+
dictionary mapping environment object names to the object names in
|
| 119 |
+
the first column of tempEmissivityNew
|
| 120 |
+
tempEmissivityNew
|
| 121 |
+
numpy array containing object names and corresponding simulated
|
| 122 |
+
thermal digital count
|
| 123 |
+
client
|
| 124 |
+
connection to AirSim (e.g., client = MultirotorClient() for UAV)
|
| 125 |
+
|
| 126 |
+
author::
|
| 127 |
+
Elizabeth Bondi
|
| 128 |
+
"""
|
| 129 |
+
|
| 130 |
+
#First set everything to 0.
|
| 131 |
+
success = client.simSetSegmentationObjectID("[\w]*", 0, True);
|
| 132 |
+
if not success:
|
| 133 |
+
print('There was a problem setting all segmentation object IDs to 0. ')
|
| 134 |
+
sys.exit(1)
|
| 135 |
+
|
| 136 |
+
#Next set all objects of interest provided to corresponding object IDs
|
| 137 |
+
#segIdDict values MUST match tempEmissivityNew labels.
|
| 138 |
+
for key in segIdDict:
|
| 139 |
+
objectID = int(tempEmissivityNew[numpy.where(tempEmissivityNew == \
|
| 140 |
+
segIdDict[key])[0],1][0])
|
| 141 |
+
|
| 142 |
+
success = client.simSetSegmentationObjectID("[\w]*"+key+"[\w]*",
|
| 143 |
+
objectID, True);
|
| 144 |
+
if not success:
|
| 145 |
+
print('There was a problem setting {0} segmentation object ID to {1!s}, or no {0} was found.'.format(key, objectID))
|
| 146 |
+
|
| 147 |
+
time.sleep(0.1)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
if __name__ == '__main__':
|
| 151 |
+
|
| 152 |
+
#Connect to AirSim, UAV mode.
|
| 153 |
+
client = MultirotorClient()
|
| 154 |
+
client.confirmConnection()
|
| 155 |
+
|
| 156 |
+
#Multiple same object settings
|
| 157 |
+
cubeList = client.simListSceneObjects('.*?Cube.*?')
|
| 158 |
+
cubeDict = {}
|
| 159 |
+
for x in cubeList:
|
| 160 |
+
cubeDict[x] = 'tree'
|
| 161 |
+
|
| 162 |
+
|
| 163 |
+
segIdDict = {
|
| 164 |
+
'Floor':'soil',
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
segIdDict.update(cubeDict)
|
| 168 |
+
#Choose temperature values for winter or summer.
|
| 169 |
+
#"""
|
| 170 |
+
#winter
|
| 171 |
+
tempEmissivity = numpy.array([['elephant',290,0.96],
|
| 172 |
+
['zebra',298,0.98],
|
| 173 |
+
['rhinoceros',291,0.96],
|
| 174 |
+
['hippopotamus',290,0.96],
|
| 175 |
+
['crocodile',295,0.96],
|
| 176 |
+
['human',292,0.985],
|
| 177 |
+
['tree',273,0.952],
|
| 178 |
+
['grass',273,0.958],
|
| 179 |
+
['soil',278,0.914],
|
| 180 |
+
['shrub',300,0.3],
|
| 181 |
+
['truck',273,0.8],
|
| 182 |
+
['water',273,0.96]])
|
| 183 |
+
#"""
|
| 184 |
+
"""
|
| 185 |
+
#summer
|
| 186 |
+
tempEmissivity = numpy.array([['elephant',298,0.96],
|
| 187 |
+
['zebra',307,0.98],
|
| 188 |
+
['rhinoceros',299,0.96],
|
| 189 |
+
['hippopotamus',298,0.96],
|
| 190 |
+
['crocodile',303,0.96],
|
| 191 |
+
['human',301,0.985],
|
| 192 |
+
['tree',293,0.952],
|
| 193 |
+
['grass',293,0.958],
|
| 194 |
+
['soil',288,0.914],
|
| 195 |
+
['shrub',293,0.986],
|
| 196 |
+
['truck',293,0.8],
|
| 197 |
+
['water',293,0.96]])
|
| 198 |
+
"""
|
| 199 |
+
|
| 200 |
+
#Read camera response.
|
| 201 |
+
response = None
|
| 202 |
+
camResponseFile = 'camera_response.npy'
|
| 203 |
+
try:
|
| 204 |
+
numpy.load(camResponseFile)
|
| 205 |
+
except:
|
| 206 |
+
print("{} not found. Using default response.".format(camResponseFile))
|
| 207 |
+
|
| 208 |
+
#Calculate radiance.
|
| 209 |
+
tempEmissivityNew = get_new_temp_emiss_from_radiance(tempEmissivity,
|
| 210 |
+
response)
|
| 211 |
+
|
| 212 |
+
#Set IDs in AirSim environment.
|
| 213 |
+
set_segmentation_ids(segIdDict, tempEmissivityNew, client)
|
pic/image 1.png
ADDED
|
Git LFS Details
|
pic/image 2.png
ADDED
|
pic/image 3.png
ADDED
|
pic/image_1.jpg
ADDED
|