diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..f3d5c41 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..11fc491 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..61713e7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,19 @@ +--- +name: Question +about: Ask a question about the project, setup, or use cases. +title: "[Question]" +labels: help wanted, question +assignees: '' + +--- + +Topic: + + +Tags: + + +Detailed question: + + +Hardware: diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml new file mode 100644 index 0000000..1274a39 --- /dev/null +++ b/.github/workflows/greetings.yml @@ -0,0 +1,16 @@ +name: Greetings + +on: [pull_request_target, issues] + +jobs: + greeting: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-message: "πŸ‘‹ Hi there! Welcome to the EV3Aero project β€” we're glad you're here! Thank you for opening your first issue. The ev3aero project is powered by the EV3Dev platform and community-driven innovation, so your feedback is really appreciated. One of the maintainers will review your report soon." + pr-message: "Thank you for your first contribution to EV3Aero! We’re excited to see your pull request. The ev3aero project thrives on community support like yours, and we're grateful you're helping to improve it. A maintainer will review your changes soon." diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..e4956e6 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,27 @@ +# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. +# +# You can adjust the behavior by modifying this file. +# For more information, see: +# https://github.com/actions/stale +name: Mark stale issues and pull requests + +on: + schedule: + - cron: '0 3 * * *' + +jobs: + stale: + + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - uses: actions/stale@v5 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: 'Stale issue message' + stale-pr-message: 'Stale pull request message' + stale-issue-label: 'no-issue-activity' + stale-pr-label: 'no-pr-activity' diff --git a/Ev3Areo/Client/Ev3Aero/main.py b/Ev3Aero/Client/Ev3Aero/main.py similarity index 100% rename from Ev3Areo/Client/Ev3Aero/main.py rename to Ev3Aero/Client/Ev3Aero/main.py diff --git a/Ev3Areo/Server/Main.py b/Ev3Aero/Server/Main.py similarity index 97% rename from Ev3Areo/Server/Main.py rename to Ev3Aero/Server/Main.py index 16d09aa..6704d97 100644 --- a/Ev3Areo/Server/Main.py +++ b/Ev3Aero/Server/Main.py @@ -1,97 +1,97 @@ -import socket -import pygame -import sys -import time -import vgamepad as vg # Import vgamepad - -def get_motor_positions(ip_address, port, retries=60, delay=0.5): - """Attempts to receive motor positions from the specified IP and port.""" - for attempt in range(retries): - try: - with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: - s.connect((ip_address, port)) - data = s.recv(1024) - received_str = data.decode() - # Parse the motor positions and touch state - pos_a_degrees = received_str.split(",")[0].split(":")[1].strip().split()[0] - pos_b_degrees = received_str.split(",")[1].split(":")[1].strip().split()[0] - pos_c_degrees = received_str.split(",")[2].split(":")[1].strip().split()[0] - touch_state = received_str.split(",")[3].split(":")[1].strip() - return pos_a_degrees, pos_b_degrees, pos_c_degrees, touch_state - except (IndexError, ValueError, socket.error) as e: - print(f"Error receiving motor positions (attempt {attempt + 1}/{retries}): {e}") - time.sleep(delay) - return "N/A", "N/A", "N/A", "N/A" - -def main(): - ip_address = 'EV3-IP' # [LOCAL] IP of the device sending motor data (replaced random local ip with placeholder). (if using custom connection might not be needed) - port = 12345 # Port to connect to (default port). - - # Set limits for motor positions - limit_a = 40 - limit_b = 45 - limit_c = 50 - - pygame.init() - screen = pygame.display.set_mode((400, 300)) - pygame.display.set_caption('Motor Positions') - font = pygame.font.Font(None, 36) - clock = pygame.time.Clock() - - # Initialize vGamepad (Xbox 360 Controller) - gamepad = vg.VX360Gamepad() - print("vGamepad initialized!") - - while True: - for event in pygame.event.get(): - if event.type == pygame.QUIT: - pygame.quit() - sys.exit() - - pos_a_degrees, pos_b_degrees, pos_c_degrees, touch_state = get_motor_positions(ip_address, port) - - if pos_a_degrees != "N/A": - pos_a_degrees = max(min(float(pos_a_degrees), limit_a), -limit_a) - if pos_b_degrees != "N/A": - pos_b_degrees = max(min(float(pos_b_degrees), limit_b), -limit_b) - if pos_c_degrees != "N/A": - pos_c_degrees = max(min(float(pos_c_degrees), limit_c), -limit_c) - - # Scale motor positions to joystick axis range (-32768 to 32767) - scaled_pos_a = int((float(pos_a_degrees) / limit_a) * 32767) - scaled_pos_b = int((float(pos_b_degrees) / limit_b) * 32767) - - # Scale motor C to right joystick Y-axis (-32768 to 32767) - if pos_c_degrees == "N/A": - scaled_pos_c = 0 # Default to center if data is invalid - else: - scaled_pos_c = int((float(pos_c_degrees) / limit_c) * 32767) - - # Send values to virtual gamepad - gamepad.left_joystick(x_value=scaled_pos_a, y_value=scaled_pos_b) # Left joystick - gamepad.right_joystick(x_value=0, y_value=scaled_pos_c) # Right joystick Y-axis - - # Set button state - if touch_state == "1": - gamepad.press_button(vg.XUSB_BUTTON.XUSB_GAMEPAD_A) - else: - gamepad.release_button(vg.XUSB_BUTTON.XUSB_GAMEPAD_A) - - gamepad.update() # Apply changes to virtual controller - - # Display motor positions on screen - screen.fill((255, 255, 255)) - text_a = font.render(f"Motor A Position: {pos_a_degrees} degrees", True, (0, 0, 0)) - text_b = font.render(f"Motor B Position: {pos_b_degrees} degrees", True, (0, 0, 0)) - text_c = font.render(f"Motor C Position: {pos_c_degrees} degrees", True, (0, 0, 0)) - text_touch = font.render(f"Button State: {touch_state}", True, (0, 0, 0)) - screen.blit(text_a, (20, 50)) - screen.blit(text_b, (20, 100)) - screen.blit(text_c, (20, 150)) - screen.blit(text_touch, (20, 200)) - - pygame.display.flip() - clock.tick(1000) # Update every 1 millisecond - -if __name__ == "__main__": - main() +import socket +import pygame +import sys +import time +import vgamepad as vg # Import vgamepad + +def get_motor_positions(ip_address, port, retries=60, delay=0.5): + """Attempts to receive motor positions from the specified IP and port.""" + for attempt in range(retries): + try: + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: + s.connect((ip_address, port)) + data = s.recv(1024) + received_str = data.decode() + # Parse the motor positions and touch state + pos_a_degrees = received_str.split(",")[0].split(":")[1].strip().split()[0] + pos_b_degrees = received_str.split(",")[1].split(":")[1].strip().split()[0] + pos_c_degrees = received_str.split(",")[2].split(":")[1].strip().split()[0] + touch_state = received_str.split(",")[3].split(":")[1].strip() + return pos_a_degrees, pos_b_degrees, pos_c_degrees, touch_state + except (IndexError, ValueError, socket.error) as e: + print(f"Error receiving motor positions (attempt {attempt + 1}/{retries}): {e}") + time.sleep(delay) + return "N/A", "N/A", "N/A", "N/A" + +def main(): + ip_address = 'EV3-IP' # [LOCAL] IP of the device sending motor data (replaced random local ip with placeholder). (if using custom connection might not be needed) + port = 12345 # Port to connect to (default port). + + # Set limits for motor positions + limit_a = 40 + limit_b = 45 + limit_c = 50 + + pygame.init() + screen = pygame.display.set_mode((400, 300)) + pygame.display.set_caption('Motor Positions') + font = pygame.font.Font(None, 36) + clock = pygame.time.Clock() + + # Initialize vGamepad (Xbox 360 Controller) + gamepad = vg.VX360Gamepad() + print("vGamepad initialized!") + + while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + + pos_a_degrees, pos_b_degrees, pos_c_degrees, touch_state = get_motor_positions(ip_address, port) + + if pos_a_degrees != "N/A": + pos_a_degrees = max(min(float(pos_a_degrees), limit_a), -limit_a) + if pos_b_degrees != "N/A": + pos_b_degrees = max(min(float(pos_b_degrees), limit_b), -limit_b) + if pos_c_degrees != "N/A": + pos_c_degrees = max(min(float(pos_c_degrees), limit_c), -limit_c) + + # Scale motor positions to joystick axis range (-32768 to 32767) + scaled_pos_a = int((float(pos_a_degrees) / limit_a) * 32767) + scaled_pos_b = int((float(pos_b_degrees) / limit_b) * 32767) + + # Scale motor C to right joystick Y-axis (-32768 to 32767) + if pos_c_degrees == "N/A": + scaled_pos_c = 0 # Default to center if data is invalid + else: + scaled_pos_c = int((float(pos_c_degrees) / limit_c) * 32767) + + # Send values to virtual gamepad + gamepad.left_joystick(x_value=scaled_pos_a, y_value=scaled_pos_b) # Left joystick + gamepad.right_joystick(x_value=0, y_value=scaled_pos_c) # Right joystick Y-axis + + # Set button state + if touch_state == "1": + gamepad.press_button(vg.XUSB_BUTTON.XUSB_GAMEPAD_A) + else: + gamepad.release_button(vg.XUSB_BUTTON.XUSB_GAMEPAD_A) + + gamepad.update() # Apply changes to virtual controller + + # Display motor positions on screen + screen.fill((255, 255, 255)) + text_a = font.render(f"Motor A Position: {pos_a_degrees} degrees", True, (0, 0, 0)) + text_b = font.render(f"Motor B Position: {pos_b_degrees} degrees", True, (0, 0, 0)) + text_c = font.render(f"Motor C Position: {pos_c_degrees} degrees", True, (0, 0, 0)) + text_touch = font.render(f"Button State: {touch_state}", True, (0, 0, 0)) + screen.blit(text_a, (20, 50)) + screen.blit(text_b, (20, 100)) + screen.blit(text_c, (20, 150)) + screen.blit(text_touch, (20, 200)) + + pygame.display.flip() + clock.tick(1000) # Update every 1 millisecond + +if __name__ == "__main__": + main() diff --git a/Ev3Areo/Server/requirements.txt b/Ev3Aero/Server/requirements.txt similarity index 100% rename from Ev3Areo/Server/requirements.txt rename to Ev3Aero/Server/requirements.txt diff --git a/README.md b/README.md index 3aea6f9..fa531b5 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ EV3Aero is a fully open-source flight control system designed for use with LEGO Mindstorms EV3 hardware. It enables makers, educators, and simulator enthusiasts to build programmable yokes and flight interfaces using LEGO motors and sensors. Whether you're flying in a simulator, building a home cockpit, or just experimenting, EV3Aero gives you flexible control and expandability. +Check Example-video.mp4 for a demo of how the system works. --- ## πŸ“¦ Features Overview @@ -113,6 +114,8 @@ python main.py - Note the license of the following project is important when u want to make edit's or make your own version public. Private projects aren't taken into account. - The prototype version's dont have requirement file's (yet) so you have to download package's manually using PyPI (PIP). - More info on the version's is in the version.md file +- The install guide is only gonna work on the main release, other version's **DO NOT HAVE INSTALL GUIDES** as they install differently and require manual installation! +- Check the help wanted tag if you want to contribute and do not have idea's what to improve, there is propably task's there. --- ## Mentions @@ -132,3 +135,4 @@ python main.py A Java-based replacement firmware for LEGO Mindstorms, foundational for early programmable robot systems. --- +Note: The client-side (EV3) runs on ev3dev and does not support external Python dependencies β€” all code is designed to run using built-in libraries only. diff --git a/currentversion.txt b/currentversion.txt new file mode 100644 index 0000000..36c5f9e --- /dev/null +++ b/currentversion.txt @@ -0,0 +1 @@ +This is the release version (VER 1.0.0.0) [main].