pyaidrone 2.0__tar.gz → 2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyaidrone-2.0 → pyaidrone-2.1}/PKG-INFO +13 -2
- pyaidrone-2.1/pyaidrone/aidrone_mavlink_bridge.py +397 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone.egg-info/PKG-INFO +13 -2
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone.egg-info/SOURCES.txt +1 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/setup.py +1 -1
- {pyaidrone-2.0 → pyaidrone-2.1}/README.md +0 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone/__init__.py +0 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone/aiDrone.py +0 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone/deflib.py +0 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone/edu.py +0 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone/ikeyevent.py +0 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone/packet.py +0 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone/parse.py +0 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone/sensor.py +0 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone/vision_ai.py +0 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone.egg-info/dependency_links.txt +0 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone.egg-info/requires.txt +0 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/pyaidrone.egg-info/top_level.txt +0 -0
- {pyaidrone-2.0 → pyaidrone-2.1}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyaidrone
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1
|
|
4
4
|
Summary: Library for AIDrone Products
|
|
5
5
|
Home-page: http://www.ir-brain.com
|
|
6
6
|
Author: IR-Brain
|
|
@@ -10,6 +10,17 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Requires-Python: >=3.6
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: pyserial>=3.4
|
|
14
|
+
Requires-Dist: pynput>=1.7.3
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: requires-dist
|
|
22
|
+
Dynamic: requires-python
|
|
23
|
+
Dynamic: summary
|
|
13
24
|
|
|
14
25
|
## install pyaidrone
|
|
15
26
|
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AIDrone ↔ MAVLink Bridge (GCS/QGroundControl 연동용)
|
|
3
|
+
|
|
4
|
+
요구사항:
|
|
5
|
+
pip install pymavlink pyserial
|
|
6
|
+
|
|
7
|
+
사용 예:
|
|
8
|
+
python aidrone_mavlink_bridge.py --port /dev/ttyACM0 --mav-in udp:0.0.0.0:14550 --mav-out udp:127.0.0.1:14551
|
|
9
|
+
|
|
10
|
+
설명:
|
|
11
|
+
- MAVLink 입력(명령): COMMAND_LONG(ARM/TAKEOFF/LAND), MANUAL_CONTROL(조이스틱), HEARTBEAT
|
|
12
|
+
- MAVLink 출력(텔레메트리): HEARTBEAT, SYS_STATUS(배터리), ATTITUDE(roll/pitch/yaw), LOCAL_POSITION_NED(간단 위치), DISTANCE_SENSOR(ToF)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import math
|
|
17
|
+
import time
|
|
18
|
+
import threading
|
|
19
|
+
from typing import Optional
|
|
20
|
+
|
|
21
|
+
from pymavlink import mavutil
|
|
22
|
+
|
|
23
|
+
# pyaidrone 기반
|
|
24
|
+
from pyaidrone.aiDrone import AIDrone # takeoff/landing/velocity/rotation/altitude/Open/Close :contentReference[oaicite:3]{index=3}
|
|
25
|
+
from pyaidrone.sensor import AIDroneSensor # 수신 패킷을 state로 파싱 :contentReference[oaicite:4]{index=4}
|
|
26
|
+
from pyaidrone.deflib import FRONT, BACK, RIGHT, LEFT # 방향 상수 :contentReference[oaicite:5]{index=5}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def clamp(v, lo, hi):
|
|
30
|
+
return lo if v < lo else hi if v > hi else v
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class AIDroneMavlinkBridge:
|
|
34
|
+
"""
|
|
35
|
+
pyaidrone(AIDrone)와 MAVLink(GCS)를 연결하는 브릿지.
|
|
36
|
+
- 드론 시리얼은 이 프로세스만 단독으로 엽니다.
|
|
37
|
+
- MAVLink는 UDP(권장)로 GCS와 연결합니다.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
serial_port: str,
|
|
43
|
+
mav_in: str,
|
|
44
|
+
mav_out: str,
|
|
45
|
+
sysid: int = 1,
|
|
46
|
+
compid: int = 1,
|
|
47
|
+
telemetry_hz: float = 10.0,
|
|
48
|
+
):
|
|
49
|
+
self.serial_port = serial_port
|
|
50
|
+
self.sysid = sysid
|
|
51
|
+
self.compid = compid
|
|
52
|
+
|
|
53
|
+
# AIDrone + 센서 파서
|
|
54
|
+
self.sensor = AIDroneSensor()
|
|
55
|
+
self.last_state = self.sensor.state.copy()
|
|
56
|
+
self.last_state_ts = 0.0
|
|
57
|
+
|
|
58
|
+
self.drone = AIDrone(receiveCallback=self._on_drone_packet)
|
|
59
|
+
|
|
60
|
+
# MAVLink I/O
|
|
61
|
+
self.mav_in = mavutil.mavlink_connection(mav_in, source_system=sysid, source_component=compid)
|
|
62
|
+
self.mav_out = mavutil.mavlink_connection(mav_out, source_system=sysid, source_component=compid)
|
|
63
|
+
|
|
64
|
+
self._stop = False
|
|
65
|
+
self._armed = False # AIDrone가 명시적 arm API가 없어서 내부 상태로만 관리
|
|
66
|
+
self._last_hb = 0.0
|
|
67
|
+
self._telemetry_period = 1.0 / max(1.0, telemetry_hz)
|
|
68
|
+
|
|
69
|
+
# MANUAL_CONTROL 유실 시 자동 정지(안전)
|
|
70
|
+
self._last_manual_ts = 0.0
|
|
71
|
+
self.manual_timeout_s = 0.7 # 이 시간 동안 조이스틱 명령이 없으면 정지
|
|
72
|
+
|
|
73
|
+
# yaw 누적(센서에 yaw가 없으면 “추정치”로 MAVLink에만 표시)
|
|
74
|
+
self._yaw_est = 0.0
|
|
75
|
+
self._yaw_rate_last = 0.0
|
|
76
|
+
self._yaw_est_ts = time.time()
|
|
77
|
+
|
|
78
|
+
# ---------------------------
|
|
79
|
+
# Drone callback
|
|
80
|
+
# ---------------------------
|
|
81
|
+
def _on_drone_packet(self, packet):
|
|
82
|
+
st = self.sensor.parse(packet)
|
|
83
|
+
if st:
|
|
84
|
+
self.last_state = st
|
|
85
|
+
self.last_state_ts = time.time()
|
|
86
|
+
|
|
87
|
+
# ---------------------------
|
|
88
|
+
# Lifecycle
|
|
89
|
+
# ---------------------------
|
|
90
|
+
def start(self):
|
|
91
|
+
ok = self.drone.Open(self.serial_port)
|
|
92
|
+
if not ok:
|
|
93
|
+
raise RuntimeError("AIDrone serial open failed")
|
|
94
|
+
|
|
95
|
+
t_rx = threading.Thread(target=self._rx_loop, daemon=True)
|
|
96
|
+
t_tx = threading.Thread(target=self._tx_loop, daemon=True)
|
|
97
|
+
t_safe = threading.Thread(target=self._safety_loop, daemon=True)
|
|
98
|
+
|
|
99
|
+
t_rx.start()
|
|
100
|
+
t_tx.start()
|
|
101
|
+
t_safe.start()
|
|
102
|
+
|
|
103
|
+
print("[Bridge] running. Ctrl+C to stop.")
|
|
104
|
+
try:
|
|
105
|
+
while True:
|
|
106
|
+
time.sleep(0.2)
|
|
107
|
+
except KeyboardInterrupt:
|
|
108
|
+
self._stop = True
|
|
109
|
+
time.sleep(0.3)
|
|
110
|
+
finally:
|
|
111
|
+
try:
|
|
112
|
+
self.drone.Close()
|
|
113
|
+
except Exception:
|
|
114
|
+
pass
|
|
115
|
+
|
|
116
|
+
# ---------------------------
|
|
117
|
+
# MAVLink RX (GCS -> Drone)
|
|
118
|
+
# ---------------------------
|
|
119
|
+
def _rx_loop(self):
|
|
120
|
+
while not self._stop:
|
|
121
|
+
msg = self.mav_in.recv_match(blocking=True, timeout=0.5)
|
|
122
|
+
if msg is None:
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
mtype = msg.get_type()
|
|
126
|
+
if mtype == "BAD_DATA":
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
if mtype == "COMMAND_LONG":
|
|
130
|
+
self._handle_command_long(msg)
|
|
131
|
+
elif mtype == "MANUAL_CONTROL":
|
|
132
|
+
self._handle_manual_control(msg)
|
|
133
|
+
elif mtype == "HEARTBEAT":
|
|
134
|
+
# 필요시 링크 감지용으로만 사용
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
def _ack_command(self, command: int, result: int):
|
|
138
|
+
self.mav_out.mav.command_ack_send(command, result, 0, 0, 0, 0)
|
|
139
|
+
|
|
140
|
+
def _handle_command_long(self, msg):
|
|
141
|
+
cmd = int(msg.command)
|
|
142
|
+
|
|
143
|
+
# ARM/DISARM: 실제 AIDrone에 arm 개념이 없으니 내부 플래그만 관리
|
|
144
|
+
if cmd == mavutil.mavlink.MAV_CMD_COMPONENT_ARM_DISARM:
|
|
145
|
+
arm = (msg.param1 >= 0.5)
|
|
146
|
+
self._armed = bool(arm)
|
|
147
|
+
self._ack_command(cmd, mavutil.mavlink.MAV_RESULT_ACCEPTED)
|
|
148
|
+
return
|
|
149
|
+
|
|
150
|
+
# TAKEOFF
|
|
151
|
+
if cmd == mavutil.mavlink.MAV_CMD_NAV_TAKEOFF:
|
|
152
|
+
try:
|
|
153
|
+
self.drone.takeoff()
|
|
154
|
+
self._armed = True
|
|
155
|
+
self._ack_command(cmd, mavutil.mavlink.MAV_RESULT_ACCEPTED)
|
|
156
|
+
except Exception:
|
|
157
|
+
self._ack_command(cmd, mavutil.mavlink.MAV_RESULT_FAILED)
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
# LAND
|
|
161
|
+
if cmd == mavutil.mavlink.MAV_CMD_NAV_LAND:
|
|
162
|
+
try:
|
|
163
|
+
self.drone.landing()
|
|
164
|
+
self._ack_command(cmd, mavutil.mavlink.MAV_RESULT_ACCEPTED)
|
|
165
|
+
except Exception:
|
|
166
|
+
self._ack_command(cmd, mavutil.mavlink.MAV_RESULT_FAILED)
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
# EMERGENCY STOP (지원하고 싶으면 MAV_CMD_DO_FLIGHTTERMINATION 등으로 매핑 가능)
|
|
170
|
+
if cmd == mavutil.mavlink.MAV_CMD_DO_FLIGHTTERMINATION:
|
|
171
|
+
try:
|
|
172
|
+
self.drone.emergency()
|
|
173
|
+
self._armed = False
|
|
174
|
+
self._ack_command(cmd, mavutil.mavlink.MAV_RESULT_ACCEPTED)
|
|
175
|
+
except Exception:
|
|
176
|
+
self._ack_command(cmd, mavutil.mavlink.MAV_RESULT_FAILED)
|
|
177
|
+
return
|
|
178
|
+
|
|
179
|
+
self._ack_command(cmd, mavutil.mavlink.MAV_RESULT_UNSUPPORTED)
|
|
180
|
+
|
|
181
|
+
def _handle_manual_control(self, msg):
|
|
182
|
+
"""
|
|
183
|
+
MANUAL_CONTROL: 조이스틱 입력
|
|
184
|
+
x: -1000..1000 (pitch: 전/후)
|
|
185
|
+
y: -1000..1000 (roll: 좌/우)
|
|
186
|
+
z: 0..1000 (throttle)
|
|
187
|
+
r: -1000..1000 (yaw)
|
|
188
|
+
"""
|
|
189
|
+
self._last_manual_ts = time.time()
|
|
190
|
+
|
|
191
|
+
# arming 안됐으면 제어 무시(안전)
|
|
192
|
+
if not self._armed:
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
x = int(msg.x) # forward/back
|
|
196
|
+
y = int(msg.y) # left/right
|
|
197
|
+
z = int(msg.z) # throttle
|
|
198
|
+
r = int(msg.r) # yaw
|
|
199
|
+
|
|
200
|
+
# 1) 전/후, 좌/우는 AIDrone.velocity(dir, vel 0~100)로 매핑
|
|
201
|
+
# AIDrone.velocity는 dir에 따라 부호를 내부에서 처리함(앞/뒤, 우/좌). :contentReference[oaicite:6]{index=6}
|
|
202
|
+
forward = clamp(x / 1000.0, -1.0, 1.0)
|
|
203
|
+
lateral = clamp(y / 1000.0, -1.0, 1.0)
|
|
204
|
+
|
|
205
|
+
# deadzone
|
|
206
|
+
dead = 0.12
|
|
207
|
+
if abs(forward) < dead:
|
|
208
|
+
forward = 0.0
|
|
209
|
+
if abs(lateral) < dead:
|
|
210
|
+
lateral = 0.0
|
|
211
|
+
|
|
212
|
+
vel_f = int(abs(forward) * 100)
|
|
213
|
+
vel_l = int(abs(lateral) * 100)
|
|
214
|
+
|
|
215
|
+
# 전/후
|
|
216
|
+
if vel_f > 0:
|
|
217
|
+
self.drone.velocity(FRONT if forward > 0 else BACK, vel_f)
|
|
218
|
+
|
|
219
|
+
# 좌/우: pyaidrone에서 RIGHT=2, LEFT=3 :contentReference[oaicite:7]{index=7}
|
|
220
|
+
if vel_l > 0:
|
|
221
|
+
self.drone.velocity(RIGHT if lateral > 0 else LEFT, vel_l)
|
|
222
|
+
|
|
223
|
+
# 둘 다 0이면 정지(velocity에 0 넣기)
|
|
224
|
+
if vel_f == 0 and vel_l == 0:
|
|
225
|
+
self.drone.velocity(FRONT, 0)
|
|
226
|
+
|
|
227
|
+
# 2) yaw: rotation(rot)으로 매핑 (상대 회전)
|
|
228
|
+
yaw_in = clamp(r / 1000.0, -1.0, 1.0)
|
|
229
|
+
if abs(yaw_in) >= 0.15:
|
|
230
|
+
# 입력 크기에 따라 작은 각도로 “자주” 회전
|
|
231
|
+
step_deg = int(yaw_in * 8) # 1회에 최대 ±8도
|
|
232
|
+
if step_deg != 0:
|
|
233
|
+
self.drone.rotation(step_deg)
|
|
234
|
+
self._yaw_rate_last = yaw_in
|
|
235
|
+
|
|
236
|
+
# 3) throttle: altitude(alt)로 매핑
|
|
237
|
+
# pyaidrone의 takeoff는 alt=70을 사용(단위는 cm로 보임) :contentReference[oaicite:8]{index=8}
|
|
238
|
+
# z는 0..1000 이므로 50~150cm로 스케일(원하는 범위로 조정 가능)
|
|
239
|
+
alt_cm = int(50 + (clamp(z / 1000.0, 0.0, 1.0) * 100)) # 50~150
|
|
240
|
+
try:
|
|
241
|
+
self.drone.altitude(alt_cm)
|
|
242
|
+
except Exception:
|
|
243
|
+
pass
|
|
244
|
+
|
|
245
|
+
# ---------------------------
|
|
246
|
+
# Safety: MANUAL 끊기면 정지
|
|
247
|
+
# ---------------------------
|
|
248
|
+
def _safety_loop(self):
|
|
249
|
+
while not self._stop:
|
|
250
|
+
if self._armed and (time.time() - self._last_manual_ts) > self.manual_timeout_s:
|
|
251
|
+
try:
|
|
252
|
+
# 정지
|
|
253
|
+
self.drone.velocity(FRONT, 0)
|
|
254
|
+
except Exception:
|
|
255
|
+
pass
|
|
256
|
+
time.sleep(0.1)
|
|
257
|
+
|
|
258
|
+
# ---------------------------
|
|
259
|
+
# MAVLink TX (Drone -> GCS)
|
|
260
|
+
# ---------------------------
|
|
261
|
+
def _tx_loop(self):
|
|
262
|
+
while not self._stop:
|
|
263
|
+
now = time.time()
|
|
264
|
+
|
|
265
|
+
# yaw 추정 업데이트(센서 yaw가 없으므로 r 입력 기반 아주 단순 적분)
|
|
266
|
+
dt = max(0.0, now - self._yaw_est_ts)
|
|
267
|
+
self._yaw_est_ts = now
|
|
268
|
+
# yaw_rate_last는 -1..1, 초당 약 30deg로 가정(표시용)
|
|
269
|
+
self._yaw_est += math.radians(30.0) * float(self._yaw_rate_last) * dt
|
|
270
|
+
# -pi..pi로 정규화
|
|
271
|
+
self._yaw_est = (self._yaw_est + math.pi) % (2 * math.pi) - math.pi
|
|
272
|
+
self._yaw_rate_last *= 0.85 # 서서히 감쇠
|
|
273
|
+
|
|
274
|
+
# 1Hz heartbeat
|
|
275
|
+
if now - self._last_hb >= 1.0:
|
|
276
|
+
self._send_heartbeat()
|
|
277
|
+
self._last_hb = now
|
|
278
|
+
|
|
279
|
+
# telemetry
|
|
280
|
+
st = self.last_state
|
|
281
|
+
|
|
282
|
+
self._send_sys_status(st)
|
|
283
|
+
self._send_attitude(st)
|
|
284
|
+
self._send_local_position(st)
|
|
285
|
+
self._send_distance_sensor(st)
|
|
286
|
+
|
|
287
|
+
time.sleep(self._telemetry_period)
|
|
288
|
+
|
|
289
|
+
def _send_heartbeat(self):
|
|
290
|
+
base_mode = 0
|
|
291
|
+
if self._armed:
|
|
292
|
+
base_mode |= mavutil.mavlink.MAV_MODE_FLAG_SAFETY_ARMED
|
|
293
|
+
|
|
294
|
+
self.mav_out.mav.heartbeat_send(
|
|
295
|
+
mavutil.mavlink.MAV_TYPE_QUADROTOR,
|
|
296
|
+
mavutil.mavlink.MAV_AUTOPILOT_GENERIC,
|
|
297
|
+
base_mode,
|
|
298
|
+
0, # custom_mode
|
|
299
|
+
mavutil.mavlink.MAV_STATE_ACTIVE
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
def _send_sys_status(self, st: dict):
|
|
303
|
+
# battery: pyaidrone sensor는 battery(%)를 제공 :contentReference[oaicite:9]{index=9}
|
|
304
|
+
batt_rem = int(st.get("battery", 0))
|
|
305
|
+
batt_rem = clamp(batt_rem, 0, 100)
|
|
306
|
+
|
|
307
|
+
# 전압은 센서에서 직접 안 주므로 -1(unknown). 남은%만 전달.
|
|
308
|
+
self.mav_out.mav.sys_status_send(
|
|
309
|
+
0, 0, 0, # sensors present/enabled/health
|
|
310
|
+
0, # load
|
|
311
|
+
0, # voltage_battery(mV) unknown
|
|
312
|
+
-1, # current_battery(cA) unknown
|
|
313
|
+
batt_rem,
|
|
314
|
+
0, 0, 0, 0, 0, 0
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
def _send_attitude(self, st: dict):
|
|
318
|
+
# sensor.py: roll/pitch가 short로 들어오며 단위는 보드 정의(대개 degree 또는 0.1deg일 가능성)
|
|
319
|
+
# 여기서는 “degree”라고 가정하고 rad로 변환(필요시 스케일 조정)
|
|
320
|
+
roll_raw = float(st.get("roll", 0))
|
|
321
|
+
pitch_raw = float(st.get("pitch", 0))
|
|
322
|
+
|
|
323
|
+
# 스케일 추정: 만약 값이 너무 크면(예: 300~500) 0.1deg일 가능성 → 0.1 배
|
|
324
|
+
if abs(roll_raw) > 180 or abs(pitch_raw) > 180:
|
|
325
|
+
roll_raw *= 0.1
|
|
326
|
+
pitch_raw *= 0.1
|
|
327
|
+
|
|
328
|
+
roll = math.radians(roll_raw)
|
|
329
|
+
pitch = math.radians(pitch_raw)
|
|
330
|
+
yaw = float(self._yaw_est)
|
|
331
|
+
|
|
332
|
+
self.mav_out.mav.attitude_send(
|
|
333
|
+
int(time.time() * 1000),
|
|
334
|
+
roll, pitch, yaw,
|
|
335
|
+
0.0, 0.0, 0.0
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
def _send_local_position(self, st: dict):
|
|
339
|
+
# sensor.py: pos_x/pos_y(cm), height(cm) 제공 :contentReference[oaicite:10]{index=10}
|
|
340
|
+
px_cm = float(st.get("pos_x", 0))
|
|
341
|
+
py_cm = float(st.get("pos_y", 0))
|
|
342
|
+
h_cm = float(st.get("height", 0))
|
|
343
|
+
|
|
344
|
+
x = px_cm / 100.0
|
|
345
|
+
y = py_cm / 100.0
|
|
346
|
+
z = -(h_cm / 100.0) # NED에서 z는 Down(+). 높이는 Up이므로 음수
|
|
347
|
+
|
|
348
|
+
# 속도는 센서에 없으니 0
|
|
349
|
+
self.mav_out.mav.local_position_ned_send(
|
|
350
|
+
int(time.time() * 1000),
|
|
351
|
+
x, y, z,
|
|
352
|
+
0.0, 0.0, 0.0
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
def _send_distance_sensor(self, st: dict):
|
|
356
|
+
# tof(mm) 제공 :contentReference[oaicite:11]{index=11}
|
|
357
|
+
tof_mm = int(st.get("tof", 0))
|
|
358
|
+
if tof_mm <= 0:
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
# MAVLink DISTANCE_SENSOR: cm 단위로 쓰는 경우도 있지만 메시지는 mm 기반(min/max는 cm 단위 필드 아님)
|
|
362
|
+
# 여기서는 current_distance를 mm로 그대로 넣고, min/max는 적당히 설정
|
|
363
|
+
self.mav_out.mav.distance_sensor_send(
|
|
364
|
+
int(time.time() * 1000),
|
|
365
|
+
50, # min_distance (cm) - 센서 최소
|
|
366
|
+
2000, # max_distance (cm) - 센서 최대(대략)
|
|
367
|
+
tof_mm, # current_distance (mm)
|
|
368
|
+
mavutil.mavlink.MAV_DISTANCE_SENSOR_LASER,
|
|
369
|
+
0, # id
|
|
370
|
+
mavutil.mavlink.MAV_SENSOR_ROTATION_PITCH_270, # 아래 방향
|
|
371
|
+
0 # covariance
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def main():
|
|
376
|
+
ap = argparse.ArgumentParser()
|
|
377
|
+
ap.add_argument("--port", required=True, help="AIDrone serial port (e.g., COM3 or /dev/ttyACM0)")
|
|
378
|
+
ap.add_argument("--mav-in", default="udp:0.0.0.0:14550", help="MAVLink input (GCS -> bridge)")
|
|
379
|
+
ap.add_argument("--mav-out", default="udp:127.0.0.1:14551", help="MAVLink output (bridge -> GCS)")
|
|
380
|
+
ap.add_argument("--sysid", type=int, default=1)
|
|
381
|
+
ap.add_argument("--compid", type=int, default=1)
|
|
382
|
+
ap.add_argument("--hz", type=float, default=10.0)
|
|
383
|
+
args = ap.parse_args()
|
|
384
|
+
|
|
385
|
+
bridge = AIDroneMavlinkBridge(
|
|
386
|
+
serial_port=args.port,
|
|
387
|
+
mav_in=args.mav_in,
|
|
388
|
+
mav_out=args.mav_out,
|
|
389
|
+
sysid=args.sysid,
|
|
390
|
+
compid=args.compid,
|
|
391
|
+
telemetry_hz=args.hz,
|
|
392
|
+
)
|
|
393
|
+
bridge.start()
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
if __name__ == "__main__":
|
|
397
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pyaidrone
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1
|
|
4
4
|
Summary: Library for AIDrone Products
|
|
5
5
|
Home-page: http://www.ir-brain.com
|
|
6
6
|
Author: IR-Brain
|
|
@@ -10,6 +10,17 @@ Classifier: License :: OSI Approved :: MIT License
|
|
|
10
10
|
Classifier: Operating System :: OS Independent
|
|
11
11
|
Requires-Python: >=3.6
|
|
12
12
|
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: pyserial>=3.4
|
|
14
|
+
Requires-Dist: pynput>=1.7.3
|
|
15
|
+
Dynamic: author
|
|
16
|
+
Dynamic: author-email
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: home-page
|
|
21
|
+
Dynamic: requires-dist
|
|
22
|
+
Dynamic: requires-python
|
|
23
|
+
Dynamic: summary
|
|
13
24
|
|
|
14
25
|
## install pyaidrone
|
|
15
26
|
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="pyaidrone",
|
|
5
|
-
version="2.
|
|
5
|
+
version="2.1",
|
|
6
6
|
description="Library for AIDrone Products",
|
|
7
7
|
long_description=open("README.md").read(), # README.md 내용을 long_description으로 사용
|
|
8
8
|
long_description_content_type="text/markdown", # README 파일이 markdown 형식임을 지정
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|