Skip to content

Commit cc1d205

Browse files
committed
refactoed recevier, updated GUI for optimal touchscreen
1 parent 4b13676 commit cc1d205

File tree

6 files changed

+814
-129
lines changed

6 files changed

+814
-129
lines changed
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
"""
2+
MAVSDK Offboard Control - Attitude Rate Control Sender
3+
======================================================
4+
5+
This script provides an interface for controlling a drone's attitude rates (roll, pitch, yaw rates) and thrust
6+
through keyboard inputs, utilizing MAVSDK over UDP. It features an interactive GUI built with Pygame
7+
for real-time control and feedback, enabling dynamic adjustment of the drone's flight parameters.
8+
9+
Overview:
10+
---------
11+
- Sends control packets to command drone attitude rates and thrust in local body coordinates.
12+
- Offers two modes of operation: 'Instant Reset' and 'Incremental Control', toggled by pressing 'M'.
13+
- Provides a graphical interface to visualize and control the drone's attitude rates and thrust.
14+
15+
Setup Requirements:
16+
-------------------
17+
- A MAVSDK-compatible drone or a SITL setup running and accessible on the network.
18+
- The receiver node (`receiver.py`) must be operational to handle and execute the commands sent from this script.
19+
- Ensure that the receiver and this sender script are configured to communicate over the specified IP and port.
20+
21+
Key Functionalities:
22+
--------------------
23+
- **Attitude Rate Control**: Use W, S, A, D for adjusting pitch and roll rates.
24+
- W: Decrease pitch rate (nose down)
25+
- S: Increase pitch rate (nose up)
26+
- A: Decrease roll rate (left down)
27+
- D: Increase roll rate (right down)
28+
- **Thrust Adjustment**: Up and Down arrow keys adjust thrust.
29+
- **Yaw Rate Control**: Left and Right arrow keys adjust yaw rate.
30+
- **Mode Switching**: Press 'M' to toggle between 'Instant Reset' and 'Incremental Control' modes.
31+
- **Control Enable/Disable**: 'E' to enable sending commands, 'C' to cancel and send a stop command.
32+
- **Emergency Hold**: Press 'H' to immediately hold the current attitude rates and thrust, effectively stopping any adjustments.
33+
- **Application Exit**: Press 'Q' to safely exit the application, ensuring all movements are halted.
34+
35+
Usage Instructions:
36+
-------------------
37+
1. Ensure your MAVSDK setup (either SITL or a real drone) is operational and that `receiver.py` is running.
38+
2. Start this script in a Python environment where Pygame is installed. The script's GUI will display on your screen.
39+
3. Use the keyboard controls as outlined to command the drone. Ensure you start command transmission by pressing 'E' and can stop it anytime with 'H' or 'C'.
40+
41+
Safety Notice:
42+
--------------
43+
- When operating with a real drone, ensure you are in a safe, open environment to avoid any accidents.
44+
- Always be prepared to take manual control of the drone if necessary.
45+
46+
Author:
47+
-------
48+
- Alireza Ghaderi
49+
- GitHub: alireza787b
50+
- Date: May 2024
51+
52+
Dependencies:
53+
-------------
54+
- Pygame for GUI operations.
55+
- MAVSDK for drone control interfacing.
56+
- Python's `socket` library for UDP communication.
57+
- `control_packet.py` for formatting control commands.
58+
59+
The code is designed to be clear and modifiable for different use cases, allowing adjustments to IP settings, control rates, and more directly within the script.
60+
61+
"""
62+
import socket
63+
import pygame
64+
import sys
65+
from control_packet import ControlPacket, SetpointMode
66+
67+
# Constants for communication and control
68+
UDP_IP = "127.0.0.1"
69+
UDP_PORT = 5005
70+
SEND_RATE = 0.1 # Packet send rate in seconds (10 Hz)
71+
ROLL_PITCH_RATE_STEP = 2.0 # degrees per second step for roll and pitch rate
72+
YAW_RATE_STEP = 5.0 # degrees per second step for yaw rate
73+
THRUST_STEP = 0.02 # thrust step
74+
INCREMENTAL_MODE = False # False for instant reset, True for incremental control
75+
76+
# Initialize Pygame and set up the display
77+
pygame.init()
78+
screen = pygame.display.set_mode((800, 600))
79+
pygame.display.set_caption('MAVSDK Offboard Control - Attitude Rate Control')
80+
81+
# Colors, fonts, and initial settings
82+
BACKGROUND_COLOR = (30, 30, 30)
83+
TEXT_COLOR = (255, 255, 255)
84+
FONT = pygame.font.Font(None, 36)
85+
SMALL_FONT = pygame.font.Font(None, 24)
86+
GREEN = (0, 255, 0)
87+
RED = (255, 0, 0)
88+
89+
# Setup UDP socket for sending commands
90+
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
91+
92+
def send_attitude_rate(roll_rate, pitch_rate, yaw_rate, thrust):
93+
"""Send an attitude rate command to the drone."""
94+
packet = ControlPacket(
95+
mode=SetpointMode.ATTITUDE_RATE_CONTROL,
96+
enable_flag=True,
97+
yaw_control_flag=True,
98+
position=(0, 0, 0), # Not used in attitude rate mode
99+
velocity=(0, 0, 0), # Not used in attitude rate mode
100+
acceleration=(0, 0, 0), # Not used in attitude rate mode
101+
attitude=(0, 0, 0, thrust), # Thrust only
102+
attitude_rate=(roll_rate, pitch_rate, yaw_rate)
103+
)
104+
packed_data = packet.pack()
105+
sock.sendto(packed_data, (UDP_IP, UDP_PORT))
106+
107+
def display_text(message, position, font=FONT, color=TEXT_COLOR):
108+
"""Displays text on the Pygame screen at the given position."""
109+
text = font.render(message, True, color)
110+
screen.blit(text, position)
111+
112+
class Button:
113+
"""Button class to create interactive GUI buttons."""
114+
def __init__(self, text, position, size, action, release_action=None):
115+
self.text = text
116+
self.position = position
117+
self.size = size
118+
self.action = action
119+
self.release_action = release_action
120+
self.color = (100, 100, 100)
121+
self.active = False
122+
123+
def draw(self, screen):
124+
color = (150, 150, 150) if self.active else self.color
125+
pygame.draw.rect(screen, color, (*self.position, *self.size))
126+
text_surface = SMALL_FONT.render(self.text, True, TEXT_COLOR)
127+
text_rect = text_surface.get_rect(center=(self.position[0] + self.size[0] // 2, self.position[1] + self.size[1] // 2))
128+
screen.blit(text_surface, text_rect)
129+
130+
def click(self):
131+
self.active = True
132+
self.action()
133+
134+
def release(self):
135+
self.active = False
136+
if self.release_action:
137+
self.release_action()
138+
139+
def is_clicked(self, mouse_pos):
140+
x, y = mouse_pos
141+
return (self.position[0] <= x <= self.position[0] + self.size[0]) and (self.position[1] <= y <= self.position[1] + self.size[1])
142+
143+
# Movement control variables
144+
roll_rate, pitch_rate, yaw_rate, thrust = 0, 0, 0, 0.5 # Start with a neutral thrust value
145+
enabled = False
146+
147+
# Button actions
148+
def enable_control():
149+
global enabled
150+
enabled = True
151+
152+
def disable_control():
153+
global enabled
154+
enabled = False
155+
send_attitude_rate(0, 0, 0, 0)
156+
157+
def reset_control():
158+
global roll_rate, pitch_rate, yaw_rate, thrust
159+
roll_rate, pitch_rate, yaw_rate, thrust = 0, 0, 0, 0.5 # Reset to neutral thrust
160+
161+
def adjust_pitch_rate_up():
162+
global pitch_rate
163+
pitch_rate += ROLL_PITCH_RATE_STEP if INCREMENTAL_MODE else ROLL_PITCH_RATE_STEP
164+
165+
def adjust_pitch_rate_down():
166+
global pitch_rate
167+
pitch_rate -= ROLL_PITCH_RATE_STEP if INCREMENTAL_MODE else ROLL_PITCH_RATE_STEP
168+
169+
def adjust_roll_rate_left():
170+
global roll_rate
171+
roll_rate -= ROLL_PITCH_RATE_STEP if INCREMENTAL_MODE else ROLL_PITCH_RATE_STEP
172+
173+
def adjust_roll_rate_right():
174+
global roll_rate
175+
roll_rate += ROLL_PITCH_RATE_STEP if INCREMENTAL_MODE else ROLL_PITCH_RATE_STEP
176+
177+
def increase_thrust():
178+
global thrust
179+
thrust = min(thrust + THRUST_STEP, 1.0) # Ensure thrust does not exceed 1
180+
181+
def decrease_thrust():
182+
global thrust
183+
thrust = max(thrust - THRUST_STEP, 0) # Ensure thrust does not go below 0
184+
185+
def yaw_rate_left():
186+
global yaw_rate
187+
yaw_rate -= YAW_RATE_STEP if not INCREMENTAL_MODE else (yaw_rate - YAW_RATE_STEP)
188+
189+
def yaw_rate_right():
190+
global yaw_rate
191+
yaw_rate += YAW_RATE_STEP if not INCREMENTAL_MODE else (yaw_rate + YAW_RATE_STEP)
192+
193+
def toggle_mode():
194+
global INCREMENTAL_MODE
195+
INCREMENTAL_MODE = not INCREMENTAL_MODE
196+
197+
# Reset functions for instant return mode
198+
def reset_roll_rate():
199+
global roll_rate
200+
roll_rate = 0
201+
202+
def reset_pitch_rate():
203+
global pitch_rate
204+
pitch_rate = 0
205+
206+
def reset_thrust():
207+
# global thrust
208+
# thrust = 0.5 # Reset to mid thrust
209+
pass
210+
211+
def reset_yaw_rate():
212+
global yaw_rate
213+
yaw_rate = 0
214+
215+
# Button actions
216+
def check_enabled(action):
217+
"""Decorator-like function to execute the action only if controls are enabled."""
218+
def wrapper():
219+
if enabled:
220+
action()
221+
return wrapper
222+
223+
# Wrapper function to reset only if incremental mode is not active
224+
def check_and_reset(action):
225+
"""Decorator-like function to reset only if incremental mode is not active."""
226+
def wrapper():
227+
if not INCREMENTAL_MODE:
228+
action()
229+
return wrapper
230+
231+
# Button initialization with joystick-style layout
232+
buttons = [
233+
Button('Enable', (50, 150), (100, 50), enable_control),
234+
Button('Disable', (50, 220), (100, 50), disable_control),
235+
Button('Hold', (50, 290), (100, 50), reset_control),
236+
Button('Mode', (50, 360), (100, 50), toggle_mode), # Mode toggle button
237+
Button('Pitch Up', (600, 290), (100, 50), check_enabled(adjust_pitch_rate_up), check_and_reset(reset_pitch_rate)),
238+
Button('Pitch Down', (600, 150), (100, 50), check_enabled(adjust_pitch_rate_down), check_and_reset(reset_pitch_rate)),
239+
Button('Roll Left', (500, 220), (100, 50), check_enabled(adjust_roll_rate_left), check_and_reset(reset_roll_rate)),
240+
Button('Roll Right', (700, 220), (100, 50), check_enabled(adjust_roll_rate_right), check_and_reset(reset_roll_rate)),
241+
Button('Increase Thrust', (275, 150), (150, 50), check_enabled(increase_thrust), check_and_reset(reset_thrust)),
242+
Button('Decrease Thrust', (275, 290), (150, 50), check_enabled(decrease_thrust), check_and_reset(reset_thrust)),
243+
Button('Yaw Left', (200, 220), (100, 50), check_enabled(yaw_rate_left), check_and_reset(reset_yaw_rate)),
244+
Button('Yaw Right', (375, 220), (100, 50), check_enabled(yaw_rate_right), check_and_reset(reset_yaw_rate))
245+
]
246+
247+
def main():
248+
"""Main function to handle keyboard and mouse inputs for drone attitude rate control."""
249+
global INCREMENTAL_MODE, roll_rate, pitch_rate, yaw_rate, thrust
250+
running = True
251+
clock = pygame.time.Clock()
252+
253+
while running:
254+
screen.fill(BACKGROUND_COLOR)
255+
display_text("MAVSDK Offboard Control: Attitude Rate Control", (50, 20), font=FONT)
256+
display_text("Press 'E' to enable, 'C' to cancel, 'M' to toggle mode, 'H' to hold, 'Q' to quit", (50, 50), font=SMALL_FONT)
257+
mode_text = "Incremental" if INCREMENTAL_MODE else "Instant Reset"
258+
display_text(f"Mode: {mode_text}", (50, 80), font=SMALL_FONT)
259+
if enabled:
260+
display_text("Status: Enabled", (50, 100), font=SMALL_FONT, color=GREEN)
261+
else:
262+
display_text("Status: Disabled", (50, 100), font=SMALL_FONT, color=RED)
263+
display_text(f"Current Command: Roll Rate={roll_rate:.2f}, Pitch Rate={pitch_rate:.2f}, Yaw Rate={yaw_rate:.2f}, Thrust={thrust:.2f}", (50, 500), font=SMALL_FONT)
264+
display_text(f"IP: {UDP_IP}, Port: {UDP_PORT}, Rate: {SEND_RATE}s", (50, 550), font=SMALL_FONT)
265+
266+
for event in pygame.event.get():
267+
if event.type == pygame.QUIT:
268+
running = False
269+
elif event.type == pygame.KEYDOWN:
270+
if event.key == pygame.K_q:
271+
send_attitude_rate(0, 0, 0, 0) # Safety stop
272+
running = False
273+
elif event.key == pygame.K_e:
274+
enable_control()
275+
elif event.key == pygame.K_c:
276+
disable_control()
277+
elif event.key == pygame.K_m:
278+
toggle_mode()
279+
elif event.key == pygame.K_h:
280+
reset_control()
281+
282+
if enabled:
283+
if event.key == pygame.K_w:
284+
adjust_pitch_rate_up()
285+
elif event.key == pygame.K_s:
286+
adjust_pitch_rate_down()
287+
elif event.key == pygame.K_a:
288+
adjust_roll_rate_left()
289+
elif event.key == pygame.K_d:
290+
adjust_roll_rate_right()
291+
elif event.key == pygame.K_UP:
292+
increase_thrust()
293+
elif event.key == pygame.K_DOWN:
294+
decrease_thrust()
295+
elif event.key == pygame.K_LEFT:
296+
yaw_rate_left()
297+
elif event.key == pygame.K_RIGHT:
298+
yaw_rate_right()
299+
300+
elif event.type == pygame.KEYUP:
301+
if not INCREMENTAL_MODE:
302+
if event.key in [pygame.K_w, pygame.K_s]:
303+
reset_pitch_rate()
304+
elif event.key in [pygame.K_a, pygame.K_d]:
305+
reset_roll_rate()
306+
elif event.key in [pygame.K_UP, pygame.K_DOWN]:
307+
reset_thrust()
308+
elif event.key in [pygame.K_LEFT, pygame.K_RIGHT]:
309+
reset_yaw_rate()
310+
311+
elif event.type == pygame.MOUSEBUTTONDOWN:
312+
mouse_pos = pygame.mouse.get_pos()
313+
for button in buttons:
314+
if button.is_clicked(mouse_pos):
315+
button.click()
316+
317+
elif event.type == pygame.MOUSEBUTTONUP:
318+
for button in buttons:
319+
button.release()
320+
321+
if enabled:
322+
send_attitude_rate(roll_rate, pitch_rate, yaw_rate, thrust)
323+
324+
for button in buttons:
325+
button.draw(screen)
326+
327+
pygame.display.flip()
328+
clock.tick(1 / SEND_RATE)
329+
330+
sock.close()
331+
pygame.quit()
332+
333+
if __name__ == "__main__":
334+
main()

0 commit comments

Comments
 (0)