pyaidrone 2.4__tar.gz → 2.5__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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: pyaidrone
3
- Version: 2.4
3
+ Version: 2.5
4
4
  Summary: Library for AIDrone Products
5
5
  Home-page: http://www.ir-brain.com
6
6
  Author: IR-Brain
@@ -10,17 +10,6 @@ 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
24
13
 
25
14
  ## install pyaidrone
26
15
 
@@ -44,17 +44,15 @@ class AIDrone(Parse, Packet):
44
44
  print(f"[receiveHandler] 일시 오류 (계속 실행): {e}")
45
45
 
46
46
 
47
- def Open(self, portName = "None"):
48
- # [수정] 문자열 "None" 비교 → None 객체 비교
49
- if portName == "None":
47
+ def Open(self, portName = None):
48
+ if portName is None:
50
49
  nodes = comports()
51
50
  for node in nodes:
52
51
  if "CH340" in node.description:
53
52
  portName = node.device
54
53
 
55
- if portName == "None":
54
+ if portName is None:
56
55
  print("Can't find Serial Port")
57
- # [수정] exit() → raise로 변경 (Jupyter/GUI 환경 보호)
58
56
  raise ConnectionError("CH340 시리얼 포트를 찾을 수 없습니다.")
59
57
  try:
60
58
  self.serial = serial.Serial(port=portName, baudrate=19200, timeout=1)
@@ -106,8 +104,6 @@ class AIDrone(Parse, Packet):
106
104
  alt = 70
107
105
  data = alt.to_bytes(2, byteorder="little", signed=False)
108
106
  self.makepkt.makePacket(12, data)
109
- alt = 0x2F
110
- data = alt.to_bytes(2, byteorder="little", signed=False)
111
107
  self.setOption(0x2F)
112
108
 
113
109
 
@@ -152,7 +148,12 @@ class AIDrone(Parse, Packet):
152
148
 
153
149
 
154
150
  def rotation(self, rot=90):
155
- self.rot += rot
151
+ self.rot += rot
152
+ # 프로토콜: YAW는 절대 회전각 -180 ~ 180 degree
153
+ if self.rot > 180:
154
+ self.rot -= 360
155
+ elif self.rot < -180:
156
+ self.rot += 360
156
157
  data = self.rot.to_bytes(2, byteorder="little", signed=True)
157
158
  self.makepkt.makePacket(10, data)
158
159
 
@@ -168,6 +169,51 @@ class AIDrone(Parse, Packet):
168
169
  def emergency(self):
169
170
  self.setOption(0x00)
170
171
  self.serial.write(self.makepkt.getPacket())
172
+
173
+ def requestSensorData(self, data_type: int, sig_mode: int = None) -> None:
174
+ """
175
+ 드론에 센서 데이터 출력을 요청합니다.
176
+
177
+ data_type 9 : 비행정보 요청 (roll / pitch / height / pos_x / pos_y)
178
+ sig_mode — 기본값 1 (출력 시작)
179
+ data_type 10 : 센서·제어 신호 요청
180
+ sig_mode — SIG 값 (POS_SIGNAL=0x06, 기타 0x01~0x05)
181
+ 기본값 0x06 (flow_x, flow_y, tof 포함)
182
+
183
+ 프로토콜 참고 (AIDrone Protocol.pdf):
184
+ TYPE(9) — Command Packet B2: 26 A8 14 B2 08 CS 09 MODE
185
+ TYPE(10) — Command Packet B1: 26 A8 14 B1 08 CS 0A SIG
186
+ """
187
+ if self.serial is None or not self.serial.isOpen():
188
+ return
189
+ if data_type == 9:
190
+ mode = 1 if sig_mode is None else (sig_mode & 0xFF)
191
+ pkt = bytearray([0x26, 0xA8, 0x14, 0xB2, 0x08, 0x00, 0x09, mode])
192
+ pkt[5] = sum(pkt[4:]) & 0xFF
193
+ self.serial.write(pkt)
194
+ elif data_type == 10:
195
+ sig = 0x06 if sig_mode is None else (sig_mode & 0xFF)
196
+ pkt = bytearray([0x26, 0xA8, 0x14, 0xB1, 0x08, 0x00, 0x0A, sig])
197
+ pkt[5] = sum(pkt[4:]) & 0xFF
198
+ self.serial.write(pkt)
199
+
200
+ def stopSensorData(self, data_type: int) -> None:
201
+ """
202
+ 드론에 센서 데이터 출력 중지를 요청합니다.
203
+
204
+ data_type 9 : 비행정보 출력 중지 (MODE=0)
205
+ data_type 10 : 센서·제어 신호 출력 중지 (STOP_SIGNAL=0x00)
206
+ """
207
+ if self.serial is None or not self.serial.isOpen():
208
+ return
209
+ if data_type == 9:
210
+ pkt = bytearray([0x26, 0xA8, 0x14, 0xB2, 0x08, 0x00, 0x09, 0x00])
211
+ pkt[5] = sum(pkt[4:]) & 0xFF
212
+ self.serial.write(pkt)
213
+ elif data_type == 10:
214
+ pkt = bytearray([0x26, 0xA8, 0x14, 0xB1, 0x08, 0x00, 0x0A, 0x00])
215
+ pkt[5] = sum(pkt[4:]) & 0xFF
216
+ self.serial.write(pkt)
171
217
 
172
218
  # aiDrone.py — AIDrone 클래스 안에 아래 3개 메서드 추가
173
219
  def setStreamAddress(self, host: str, port: int = 80, path: str = "/?action=stream"):
@@ -0,0 +1,41 @@
1
+ AIDRONE = 1
2
+
3
+
4
+ FRONT = 0
5
+ BACK = 1
6
+ RIGHT = 2
7
+ LEFT = 3
8
+
9
+ class DefLib:
10
+
11
+ @classmethod
12
+ def checksum(cls, packet):
13
+ length = packet[4]
14
+ total = 0
15
+ for n in range(6, length):
16
+ total += packet[n]
17
+ return total & 0xFF
18
+
19
+
20
+ @classmethod
21
+ def _print(cls, data):
22
+ for n in range(0, len(data)):
23
+ h = hex(data[n])
24
+ # print(h, end=" ")
25
+ # print("")
26
+
27
+
28
+ @classmethod
29
+ def constrain(cls, val, val_max, val_min):
30
+ if val > val_max:
31
+ val = val_max
32
+ if val < val_min:
33
+ val = val_min
34
+ return val
35
+
36
+
37
+ @classmethod
38
+ def comp(cls, data):
39
+ # &0xFF 결과는 항상 0~255이므로 부호 분기 불필요
40
+ return data & 0xFF
41
+
@@ -83,8 +83,8 @@ class IKeyEvent:
83
83
  self.keyTracking = False
84
84
  self.keyEsc = False
85
85
 
86
- listener = keyboard.Listener(on_press=self.on_press, on_release=self.on_release)
87
- listener.start()
86
+ self.listener = keyboard.Listener(on_press=self.on_press, on_release=self.on_release)
87
+ self.listener.start()
88
88
 
89
89
  def isKeyEnterPressed(self):
90
90
  return self.keyEnter
@@ -12,8 +12,11 @@ class Packet:
12
12
 
13
13
 
14
14
  def makePacket(self, start, data):
15
- for n in range(start, start+len(data)):
16
- self.packet[n] = data[n-start]
15
+ end = start + len(data)
16
+ if end > len(self.packet):
17
+ raise ValueError(f"makePacket: 범위 초과 (start={start}, len={len(data)}, packet={len(self.packet)})")
18
+ for n in range(start, end):
19
+ self.packet[n] = data[n - start]
17
20
  self.packet[5] = DefLib.checksum(self.packet)
18
21
  # DefLib._print(self.packet)
19
22
  return self.packet
@@ -0,0 +1,69 @@
1
+ #AIDrone sensor 라이브러리 (26.01.15)
2
+ import struct
3
+ from pyaidrone.deflib import DefLib
4
+
5
+ class AIDroneSensor:
6
+ def __init__(self):
7
+ # 센서 데이터 및 비행 상태 통합 저장소
8
+ self.state = {
9
+ 'battery': 0, # 배터리 잔량 (%)
10
+ 'tof': 0, # VL53L01X 고도 센서 값 (mm)
11
+ 'flow_x': 0, # PMW3901 광학 흐름 센서 X축 변화량
12
+ 'flow_y': 0, # PMW3901 광학 흐름 센서 Y축 변화량
13
+ 'pos_x': 0, # 이륙 지점 기준 X 좌표 (cm)
14
+ 'pos_y': 0, # 이륙 지점 기준 Y 좌표 (cm)
15
+ 'height': 0, # 드론이 계산한 현재 높이 (cm)
16
+ 'roll': 0, # 현재 좌우 기울기
17
+ 'pitch': 0, # 현재 앞뒤 기울기
18
+ 'ready': False, # 비행 준비 상태
19
+ 'emergency': False # 응급 정지 상태 여부
20
+ }
21
+
22
+ def parse(self, packet):
23
+ """
24
+ pyaidrone의 parse.py에서 수신된 패킷을 분석하여 상태를 업데이트합니다.
25
+
26
+ Output Packet 구조 (parse.py가 헤더 4바이트를 저장하지 않으므로 offset 주의):
27
+ packet[4] = LEN
28
+ packet[5] = CS
29
+ packet[6] = 0x01 (고정값)
30
+ packet[7] = OPT1
31
+ packet[8] = OPT2
32
+ packet[9] = BAT (배터리 %)
33
+ packet[10] = INFO[0] (TYPE 바이트)
34
+ packet[11+]= INFO 데이터
35
+ """
36
+ if packet is None:
37
+ return None
38
+
39
+ # parse.py에서 체크섬 검증 후 전달된 패킷이므로 헤더 재확인 불필요.
40
+ # OPT1: packet[7], BAT: packet[9], INFO: packet[10+]
41
+ opt1 = packet[7]
42
+ self.state['battery'] = packet[9]
43
+ self.state['emergency'] = bool(opt1 & 0x01)
44
+ self.state['ready'] = bool(opt1 & 0x02)
45
+
46
+ # INFO 데이터 구간 (packet[10]부터 시작, info[0]이 TYPE 바이트)
47
+ info = packet[10:]
48
+
49
+ # 2. 센서 신호 파싱 (OPT1 BIT2 활성화 시 - POS_SIGNAL 0x06)
50
+ if opt1 & 0x04:
51
+ data_type = info[0]
52
+ if data_type == 0x06:
53
+ # D1~D4 슬롯에서 Flow 및 ToF 데이터 추출 (Little Endian short)
54
+ self.state['flow_x'] = struct.unpack('<h', info[1:3])[0]
55
+ self.state['flow_y'] = struct.unpack('<h', info[3:5])[0]
56
+ self.state['tof'] = struct.unpack('<h', info[5:7])[0]
57
+
58
+ # 3. 비행 정보 파싱 (OPT1 BIT5 활성화 시 - 비행 정보 응답, TYPE=9)
59
+ if opt1 & 0x20:
60
+ # 기울기, 높이, 절대 위치(pos_x, pos_y) 추출
61
+ # info[0]=TYPE(9), info[1:3]=A1(roll), info[3:5]=A2(pitch),
62
+ # info[5:7]=H(height), info[7:9]=POS1(x), info[9:11]=POS2(y)
63
+ self.state['roll'] = struct.unpack('<h', info[1:3])[0]
64
+ self.state['pitch'] = struct.unpack('<h', info[3:5])[0]
65
+ self.state['height'] = struct.unpack('<h', info[5:7])[0]
66
+ self.state['pos_x'] = struct.unpack('<h', info[7:9])[0]
67
+ self.state['pos_y'] = struct.unpack('<h', info[9:11])[0]
68
+
69
+ return self.state
@@ -78,7 +78,13 @@ def yolo_decode(outputs, img_shape, input_shape, r, pad, threshold=0.4):
78
78
 
79
79
  class TFLiteDetector:
80
80
  def __init__(self, model_path: str):
81
+ # 초기화 실패 시 infer()에서 명확한 오류를 내기 위해 미리 None 설정
82
+ self.interpreter = None
83
+ self.input_details = None
84
+ self.output_details = None
85
+
81
86
  # 윈도우 환경(TensorFlow)과 라즈베리파이(tflite-runtime) 자동 선택
87
+ tflite = None
82
88
  try:
83
89
  import tensorflow.lite as tflite
84
90
  except ImportError:
@@ -87,14 +93,16 @@ class TFLiteDetector:
87
93
  except ImportError:
88
94
  print("❌ TFLite를 실행할 라이브러리가 없습니다.")
89
95
  return
90
-
96
+
91
97
  self.interpreter = tflite.Interpreter(model_path=model_path)
92
98
  self.interpreter.allocate_tensors()
93
99
  self.input_details = self.interpreter.get_input_details()
94
100
  self.output_details = self.interpreter.get_output_details()
95
-
101
+
96
102
  def infer(self, frame, decode_func, threshold=0.4):
97
103
  """edu.py에서 사용하는 명칭인 'infer'로 유지"""
104
+ if self.interpreter is None:
105
+ raise RuntimeError("TFLiteDetector: TFLite 라이브러리가 없어 초기화되지 않았습니다.")
98
106
  ih, iw = self.input_details[0]['shape'][1:3]
99
107
  img, r, pad = letterbox(frame, (ih, iw))
100
108
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: pyaidrone
3
- Version: 2.4
3
+ Version: 2.5
4
4
  Summary: Library for AIDrone Products
5
5
  Home-page: http://www.ir-brain.com
6
6
  Author: IR-Brain
@@ -10,17 +10,6 @@ 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
24
13
 
25
14
  ## install pyaidrone
26
15
 
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name="pyaidrone",
5
- version="2.4",
5
+ version="2.5",
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 형식임을 지정
@@ -1,44 +0,0 @@
1
- AIDRONE = 1
2
-
3
-
4
- FRONT = 0
5
- BACK = 1
6
- RIGHT = 2
7
- LEFT = 3
8
-
9
- class DefLib:
10
-
11
- @classmethod
12
- def checksum(cls, packet):
13
- len = packet[4]
14
- sum = 0
15
- for n in range(6, len):
16
- sum += packet[n]
17
- return sum & 0xFF
18
-
19
-
20
- @classmethod
21
- def _print(cls, data):
22
- for n in range(0, len(data)):
23
- h = hex(data[n])
24
- # print(h, end=" ")
25
- print("")
26
-
27
-
28
- @classmethod
29
- def constrain(cls, val , max, min):
30
- if val > max:
31
- val = max
32
- if val < min:
33
- val = min
34
- return val
35
-
36
-
37
- @classmethod
38
- def comp(cls, data):
39
- data = data&0xFF
40
- if data < 0:
41
- return 256 + data
42
- else:
43
- return data
44
-
@@ -1,58 +0,0 @@
1
- #AIDrone sensor 라이브러리 (26.01.15)
2
- import struct
3
- from pyaidrone.deflib import DefLib
4
-
5
- class AIDroneSensor:
6
- def __init__(self):
7
- # 센서 데이터 및 비행 상태 통합 저장소
8
- self.state = {
9
- 'battery': 0, # 배터리 잔량 (%)
10
- 'tof': 0, # VL53L01X 고도 센서 값 (mm)
11
- 'flow_x': 0, # PMW3901 광학 흐름 센서 X축 변화량
12
- 'flow_y': 0, # PMW3901 광학 흐름 센서 Y축 변화량
13
- 'pos_x': 0, # 이륙 지점 기준 X 좌표 (cm)
14
- 'pos_y': 0, # 이륙 지점 기준 Y 좌표 (cm)
15
- 'height': 0, # 드론이 계산한 현재 높이 (cm)
16
- 'roll': 0, # 현재 좌우 기울기
17
- 'pitch': 0, # 현재 앞뒤 기울기
18
- 'ready': False, # 비행 준비 상태
19
- 'emergency': False # 응급 정지 상태 여부
20
- }
21
-
22
- def parse(self, packet):
23
- """
24
- pyaidrone의 parse.py에서 수신된 패킷을 분석하여 상태를 업데이트합니다.
25
- """
26
- if packet is None or packet == "None":
27
- return None
28
-
29
- # 패킷 헤더 및 타입 확인 (0xA2: Output Packet)
30
- if (packet[3] & 0xF0) == 0xA0:
31
- # 1. 기본 상태 정보 추출 (배터리 및 옵션 비트)
32
- opt1 = packet[6]
33
- self.state['battery'] = packet[8]
34
- self.state['emergency'] = bool(opt1 & 0x01)
35
- self.state['ready'] = bool(opt1 & 0x02)
36
-
37
- # INFO 데이터 구간 (packet[9]부터 시작)
38
- info = packet[9:]
39
-
40
- # 2. 센서 신호 파싱 (OPT1 BIT2 활성화 시 - POS_SIGNAL 0x06)
41
- if opt1 & 0x04:
42
- data_type = info[0]
43
- if data_type == 0x06:
44
- # D1~D4 슬롯에서 Flow 및 ToF 데이터 추출 (Little Endian short)
45
- self.state['flow_x'] = struct.unpack('<h', info[1:3])[0]
46
- self.state['flow_y'] = struct.unpack('<h', info[3:5])[0]
47
- self.state['tof'] = struct.unpack('<h', info[5:7])[0]
48
-
49
- # 3. 비행 정보 파싱 (OPT1 BIT5 활성화 시 - 비행 정보 응답)
50
- if opt1 & 0x20:
51
- # 기울기, 높이, 그리고 요청하신 절대 위치(pos_x, pos_y) 추출
52
- self.state['roll'] = struct.unpack('<h', info[1:3])[0]
53
- self.state['pitch'] = struct.unpack('<h', info[3:5])[0]
54
- self.state['height'] = struct.unpack('<h', info[5:7])[0]
55
- self.state['pos_x'] = struct.unpack('<h', info[7:9])[0]
56
- self.state['pos_y'] = struct.unpack('<h', info[9:11])[0]
57
-
58
- return self.state
File without changes
File without changes
File without changes
File without changes
File without changes